fix: stable db migration + beta migration to stable
This commit is contained in:
		| @@ -6,6 +6,7 @@ const Knex = require('knex') | ||||
| const Objection = require('objection') | ||||
|  | ||||
| const migrationSource = require('../db/migrator-source') | ||||
| const migrateFromBeta = require('../db/beta') | ||||
|  | ||||
| /* global WIKI */ | ||||
|  | ||||
| @@ -110,15 +111,8 @@ module.exports = { | ||||
|     // Set init tasks | ||||
|     let conAttempts = 0 | ||||
|     let initTasks = { | ||||
|       // -> Migrate DB Schemas | ||||
|       async syncSchemas() { | ||||
|         return self.knex.migrate.latest({ | ||||
|           tableName: 'migrations', | ||||
|           migrationSource | ||||
|         }) | ||||
|       }, | ||||
|       // -> Attempt initial connection | ||||
|       async connect() { | ||||
|       async connect () { | ||||
|         try { | ||||
|           WIKI.logger.info('Connecting to database...') | ||||
|           await self.knex.raw('SELECT 1 + 1;') | ||||
| @@ -133,11 +127,23 @@ module.exports = { | ||||
|             throw err | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       // -> Migrate DB Schemas | ||||
|       async syncSchemas () { | ||||
|         return self.knex.migrate.latest({ | ||||
|           tableName: 'migrations', | ||||
|           migrationSource | ||||
|         }) | ||||
|       }, | ||||
|       // -> Migrate DB Schemas from beta | ||||
|       async migrateFromBeta () { | ||||
|         return migrateFromBeta.migrate(self.knex) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     let initTasksQueue = (WIKI.IS_MASTER) ? [ | ||||
|       initTasks.connect, | ||||
|       initTasks.migrateFromBeta, | ||||
|       initTasks.syncSchemas | ||||
|     ] : [ | ||||
|       () => { return Promise.resolve() } | ||||
|   | ||||
							
								
								
									
										115
									
								
								server/db/beta/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								server/db/beta/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| const _ = require('lodash') | ||||
| const path = require('path') | ||||
| const fs = require('fs-extra') | ||||
| const semver = require('semver') | ||||
|  | ||||
| /* global WIKI */ | ||||
|  | ||||
| module.exports = { | ||||
|   async migrate (knex) { | ||||
|     const migrationsTableExists = await knex.schema.hasTable('migrations') | ||||
|     if (!migrationsTableExists) { | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     const dbCompat = { | ||||
|       charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`) | ||||
|     } | ||||
|  | ||||
|     const migrations = await knex('migrations') | ||||
|     if (_.some(migrations, m => m.name.indexOf('2.0.0-beta') >= 0)) { | ||||
|       // -> Pre-beta.241 locale field length fix | ||||
|       const localeColnInfo = await knex('pages').columnInfo('localeCode') | ||||
|       if (WIKI.config.db.type !== 'sqlite' && localeColnInfo.maxLength === 2) { | ||||
|         // -> Load locales | ||||
|         const locales = await knex('locales') | ||||
|         await knex.schema | ||||
|           // -> Remove constraints | ||||
|           .table('users', table => { | ||||
|             table.dropForeign('localeCode') | ||||
|           }) | ||||
|           .table('pages', table => { | ||||
|             table.dropForeign('localeCode') | ||||
|           }) | ||||
|           .table('pageHistory', table => { | ||||
|             table.dropForeign('localeCode') | ||||
|           }) | ||||
|           .table('pageTree', table => { | ||||
|             table.dropForeign('localeCode') | ||||
|           }) | ||||
|           // -> Recreate locales table | ||||
|           .dropTable('locales') | ||||
|           .createTable('locales', table => { | ||||
|             if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|             table.string('code', 5).notNullable().primary() | ||||
|             table.json('strings') | ||||
|             table.boolean('isRTL').notNullable().defaultTo(false) | ||||
|             table.string('name').notNullable() | ||||
|             table.string('nativeName').notNullable() | ||||
|             table.integer('availability').notNullable().defaultTo(0) | ||||
|             table.string('createdAt').notNullable() | ||||
|             table.string('updatedAt').notNullable() | ||||
|           }) | ||||
|         await knex('locales').insert(locales) | ||||
|         // -> Alter columns length | ||||
|         await knex.schema | ||||
|           .table('users', table => { | ||||
|             table.string('localeCode', 5).notNullable().defaultTo('en').alter() | ||||
|           }) | ||||
|           .table('pages', table => { | ||||
|             table.string('localeCode', 5).alter() | ||||
|           }) | ||||
|           .table('pageHistory', table => { | ||||
|             table.string('localeCode', 5).alter() | ||||
|           }) | ||||
|           .table('pageTree', table => { | ||||
|             table.string('localeCode', 5).alter() | ||||
|           }) | ||||
|           // -> Restore restraints | ||||
|           .table('users', table => { | ||||
|             table.foreign('localeCode').references('code').inTable('locales') | ||||
|           }) | ||||
|           .table('pages', table => { | ||||
|             table.foreign('localeCode').references('code').inTable('locales') | ||||
|           }) | ||||
|           .table('pageHistory', table => { | ||||
|             table.foreign('localeCode').references('code').inTable('locales') | ||||
|           }) | ||||
|           .table('pageTree', table => { | ||||
|             table.foreign('localeCode').references('code').inTable('locales') | ||||
|           }) | ||||
|       } | ||||
|  | ||||
|       // -> Advance to latest beta/rc migration state | ||||
|       const baseMigrationPath = path.join(WIKI.SERVERPATH, (WIKI.config.db.type !== 'sqlite') ? 'db/beta/migrations' : 'db/beta/migrations-sqlite') | ||||
|       await knex.migrate.latest({ | ||||
|         tableName: 'migrations', | ||||
|         migrationSource: { | ||||
|           async getMigrations() { | ||||
|             const migrationFiles = await fs.readdir(baseMigrationPath) | ||||
|             return migrationFiles.sort(semver.compare).map(m => ({ | ||||
|               file: m, | ||||
|               directory: baseMigrationPath | ||||
|             })) | ||||
|           }, | ||||
|           getMigrationName(migration) { | ||||
|             return migration.file | ||||
|           }, | ||||
|           getMigration(migration) { | ||||
|             return require(path.join(baseMigrationPath, migration.file)) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|       // -> Cleanup migration table | ||||
|       await knex('migrations').truncate() | ||||
|  | ||||
|       // -> Advance to stable 2.0 migration state | ||||
|       await knex('migrations').insert({ | ||||
|         name: '2.0.0.js', | ||||
|         batch: 1, | ||||
|         migration_time: knex.fn.now() | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -69,7 +69,7 @@ exports.up = knex => { | ||||
|     // LOCALES -----------------------------
 | ||||
|     .createTable('locales', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('code', 5).notNullable().primary() | ||||
|       table.string('code', 2).notNullable().primary() | ||||
|       table.json('strings') | ||||
|       table.boolean('isRTL').notNullable().defaultTo(false) | ||||
|       table.string('name').notNullable() | ||||
| @@ -243,26 +243,26 @@ exports.up = knex => { | ||||
|     .table('pageHistory', table => { | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages') | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.string('localeCode', 2).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('pages', table => { | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.string('localeCode', 2).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|       table.integer('creatorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('pageTree', table => { | ||||
|       table.integer('parent').unsigned().references('id').inTable('pageTree') | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.string('localeCode', 2).references('code').inTable('locales') | ||||
|     }) | ||||
|     .table('userKeys', table => { | ||||
|       table.integer('userId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('users', table => { | ||||
|       table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en') | ||||
|       table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en') | ||||
|       table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown') | ||||
| 
 | ||||
|       table.unique(['providerKey', 'email']) | ||||
| @@ -1,10 +1,11 @@ | ||||
| /* global WIKI */ | ||||
| 
 | ||||
| exports.up = knex => { | ||||
| exports.up = async knex => { | ||||
|   const dbCompat = { | ||||
|     charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`), | ||||
|     selfCascadeDelete: WIKI.config.db.type !== 'mssql' | ||||
|   } | ||||
| 
 | ||||
|   return knex.schema | ||||
|     .dropTable('pageTree') | ||||
|     .createTable('pageTree', table => { | ||||
							
								
								
									
										20
									
								
								server/db/beta/migrations/2.0.0-rc.29.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								server/db/beta/migrations/2.0.0-rc.29.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| /* global WIKI */ | ||||
|  | ||||
| exports.up = knex => { | ||||
|   return knex.schema | ||||
|     .table('pages', table => { | ||||
|       switch (WIKI.config.db.type) { | ||||
|         case 'mariadb': | ||||
|         case 'mysql': | ||||
|           table.specificType('content', 'LONGTEXT').alter() | ||||
|           table.specificType('render', 'LONGTEXT').alter() | ||||
|           break | ||||
|         case 'mssql': | ||||
|           table.specificType('content', 'VARCHAR(max)').alter() | ||||
|           table.specificType('render', 'VARCHAR(max)').alter() | ||||
|           break | ||||
|       } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.down = knex => { } | ||||
							
								
								
									
										268
									
								
								server/db/migrations-sqlite/2.0.0.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								server/db/migrations-sqlite/2.0.0.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| exports.up = knex => { | ||||
|   return knex.schema | ||||
|     // ===================================== | ||||
|     // MODEL TABLES | ||||
|     // ===================================== | ||||
|     // ANALYTICS --------------------------- | ||||
|     .createTable('analytics', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|     }) | ||||
|     // ASSETS ------------------------------ | ||||
|     .createTable('assets', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('filename').notNullable() | ||||
|       table.string('hash').notNullable().defaultTo('') | ||||
|       table.string('ext').notNullable() | ||||
|       table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary') | ||||
|       table.string('mime').notNullable().defaultTo('application/octet-stream') | ||||
|       table.integer('fileSize').unsigned().comment('In kilobytes') | ||||
|       table.json('metadata') | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|  | ||||
|       table.integer('folderId').unsigned().references('id').inTable('assetFolders') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     // ASSET DATA -------------------------- | ||||
|     .createTable('assetData', table => { | ||||
|       table.integer('id').primary() | ||||
|       table.binary('data').notNullable() | ||||
|     }) | ||||
|     // ASSET FOLDERS ----------------------- | ||||
|     .createTable('assetFolders', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('name').notNullable() | ||||
|       table.string('slug').notNullable() | ||||
|       table.integer('parentId').unsigned().references('id').inTable('assetFolders') | ||||
|     }) | ||||
|     // AUTHENTICATION ---------------------- | ||||
|     .createTable('authentication', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|       table.boolean('selfRegistration').notNullable().defaultTo(false) | ||||
|       table.json('domainWhitelist').notNullable() | ||||
|       table.json('autoEnrollGroups').notNullable() | ||||
|     }) | ||||
|     // COMMENTS ---------------------------- | ||||
|     .createTable('comments', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.text('content').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|  | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     // EDITORS ----------------------------- | ||||
|     .createTable('editors', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|     }) | ||||
|     // GROUPS ------------------------------ | ||||
|     .createTable('groups', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('name').notNullable() | ||||
|       table.json('permissions').notNullable() | ||||
|       table.json('pageRules').notNullable() | ||||
|       table.boolean('isSystem').notNullable().defaultTo(false) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // LOCALES ----------------------------- | ||||
|     .createTable('locales', table => { | ||||
|       table.string('code', 5).notNullable().primary() | ||||
|       table.json('strings') | ||||
|       table.boolean('isRTL').notNullable().defaultTo(false) | ||||
|       table.string('name').notNullable() | ||||
|       table.string('nativeName').notNullable() | ||||
|       table.integer('availability').notNullable().defaultTo(0) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // LOGGING ---------------------------- | ||||
|     .createTable('loggers', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.string('level').notNullable().defaultTo('warn') | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // NAVIGATION ---------------------------- | ||||
|     .createTable('navigation', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // PAGE HISTORY ------------------------ | ||||
|     .createTable('pageHistory', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.string('hash').notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.string('description') | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isPublished').notNullable().defaultTo(false) | ||||
|       table.string('publishStartDate') | ||||
|       table.string('publishEndDate') | ||||
|       table.text('content') | ||||
|       table.string('contentType').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('action').defaultTo('updated') | ||||
|  | ||||
|       table.integer('pageId').unsigned() | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     // PAGE LINKS -------------------------- | ||||
|     .createTable('pageLinks', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.string('path').notNullable() | ||||
|       table.string('localeCode', 5).notNullable() | ||||
|     }) | ||||
|     // PAGES ------------------------------- | ||||
|     .createTable('pages', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.string('hash').notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.string('description') | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isPublished').notNullable().defaultTo(false) | ||||
|       table.string('privateNS') | ||||
|       table.string('publishStartDate') | ||||
|       table.string('publishEndDate') | ||||
|       table.text('content') | ||||
|       table.text('render') | ||||
|       table.json('toc') | ||||
|       table.string('contentType').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|  | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|       table.integer('creatorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     // PAGE TREE --------------------------- | ||||
|     .createTable('pageTree', table => { | ||||
|       table.integer('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.integer('depth').unsigned().notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isFolder').notNullable().defaultTo(false) | ||||
|       table.string('privateNS') | ||||
|  | ||||
|       table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE') | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|     }) | ||||
|     // RENDERERS --------------------------- | ||||
|     .createTable('renderers', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // SEARCH ------------------------------ | ||||
|     .createTable('searchEngines', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // SETTINGS ---------------------------- | ||||
|     .createTable('settings', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.json('value') | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // STORAGE ----------------------------- | ||||
|     .createTable('storage', table => { | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push') | ||||
|       table.json('config') | ||||
|       table.string('syncInterval') | ||||
|       table.json('state') | ||||
|     }) | ||||
|     // TAGS -------------------------------- | ||||
|     .createTable('tags', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('tag').notNullable().unique() | ||||
|       table.string('title') | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // USER KEYS --------------------------- | ||||
|     .createTable('userKeys', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('kind').notNullable() | ||||
|       table.string('token').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('validUntil').notNullable() | ||||
|  | ||||
|       table.integer('userId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     // USERS ------------------------------- | ||||
|     .createTable('users', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.string('email').notNullable() | ||||
|       table.string('name').notNullable() | ||||
|       table.string('providerId') | ||||
|       table.string('password') | ||||
|       table.boolean('tfaIsActive').notNullable().defaultTo(false) | ||||
|       table.string('tfaSecret') | ||||
|       table.string('jobTitle').defaultTo('') | ||||
|       table.string('location').defaultTo('') | ||||
|       table.string('pictureUrl') | ||||
|       table.string('timezone').notNullable().defaultTo('America/New_York') | ||||
|       table.boolean('isSystem').notNullable().defaultTo(false) | ||||
|       table.boolean('isActive').notNullable().defaultTo(false) | ||||
|       table.boolean('isVerified').notNullable().defaultTo(false) | ||||
|       table.boolean('mustChangePwd').notNullable().defaultTo(false) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|  | ||||
|       table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en') | ||||
|       table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown') | ||||
|     }) | ||||
|     // ===================================== | ||||
|     // RELATION TABLES | ||||
|     // ===================================== | ||||
|     // PAGE HISTORY TAGS --------------------------- | ||||
|     .createTable('pageHistoryTags', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE') | ||||
|       table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') | ||||
|     }) | ||||
|     // PAGE TAGS --------------------------- | ||||
|     .createTable('pageTags', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') | ||||
|     }) | ||||
|     // USER GROUPS ------------------------- | ||||
|     .createTable('userGroups', table => { | ||||
|       table.increments('id').primary() | ||||
|       table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE') | ||||
|       table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE') | ||||
|     }) | ||||
|     // ===================================== | ||||
|     // REFERENCES | ||||
|     // ===================================== | ||||
|     .table('users', table => { | ||||
|       table.unique(['providerKey', 'email']) | ||||
|     }) | ||||
|     // ===================================== | ||||
|     // INDEXES | ||||
|     // ===================================== | ||||
|     .table('pageLinks', table => { | ||||
|       table.index(['path', 'localeCode']) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.down = knex => { } | ||||
							
								
								
									
										325
									
								
								server/db/migrations/2.0.0.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								server/db/migrations/2.0.0.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | ||||
| /* global WIKI */ | ||||
|  | ||||
| exports.up = knex => { | ||||
|   const dbCompat = { | ||||
|     blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`), | ||||
|     charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`), | ||||
|     selfCascadeDelete: WIKI.config.db.type !== 'mssql' | ||||
|   } | ||||
|   return knex.schema | ||||
|     // ===================================== | ||||
|     // MODEL TABLES | ||||
|     // ===================================== | ||||
|     // ANALYTICS --------------------------- | ||||
|     .createTable('analytics', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|     }) | ||||
|     // ASSETS ------------------------------ | ||||
|     .createTable('assets', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('filename').notNullable() | ||||
|       table.string('hash').notNullable() | ||||
|       table.string('ext').notNullable() | ||||
|       table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary') | ||||
|       table.string('mime').notNullable().defaultTo('application/octet-stream') | ||||
|       table.integer('fileSize').unsigned().comment('In kilobytes') | ||||
|       table.json('metadata') | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // ASSET DATA -------------------------- | ||||
|     .createTable('assetData', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.integer('id').primary() | ||||
|       if (dbCompat.blobLength) { | ||||
|         table.specificType('data', 'LONGBLOB').notNullable() | ||||
|       } else { | ||||
|         table.binary('data').notNullable() | ||||
|       } | ||||
|     }) | ||||
|     // ASSET FOLDERS ----------------------- | ||||
|     .createTable('assetFolders', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('name').notNullable() | ||||
|       table.string('slug').notNullable() | ||||
|       table.integer('parentId').unsigned().references('id').inTable('assetFolders') | ||||
|     }) | ||||
|     // AUTHENTICATION ---------------------- | ||||
|     .createTable('authentication', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|       table.boolean('selfRegistration').notNullable().defaultTo(false) | ||||
|       table.json('domainWhitelist').notNullable() | ||||
|       table.json('autoEnrollGroups').notNullable() | ||||
|     }) | ||||
|     // COMMENTS ---------------------------- | ||||
|     .createTable('comments', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.text('content').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // EDITORS ----------------------------- | ||||
|     .createTable('editors', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config').notNullable() | ||||
|     }) | ||||
|     // GROUPS ------------------------------ | ||||
|     .createTable('groups', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('name').notNullable() | ||||
|       table.json('permissions').notNullable() | ||||
|       table.json('pageRules').notNullable() | ||||
|       table.boolean('isSystem').notNullable().defaultTo(false) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // LOCALES ----------------------------- | ||||
|     .createTable('locales', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('code', 5).notNullable().primary() | ||||
|       table.json('strings') | ||||
|       table.boolean('isRTL').notNullable().defaultTo(false) | ||||
|       table.string('name').notNullable() | ||||
|       table.string('nativeName').notNullable() | ||||
|       table.integer('availability').notNullable().defaultTo(0) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // LOGGING ---------------------------- | ||||
|     .createTable('loggers', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.string('level').notNullable().defaultTo('warn') | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // NAVIGATION ---------------------------- | ||||
|     .createTable('navigation', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // PAGE HISTORY ------------------------ | ||||
|     .createTable('pageHistory', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.string('hash').notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.string('description') | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isPublished').notNullable().defaultTo(false) | ||||
|       table.string('publishStartDate') | ||||
|       table.string('publishEndDate') | ||||
|       table.string('action').defaultTo('updated') | ||||
|       table.integer('pageId').unsigned() | ||||
|       table.text('content') | ||||
|       table.string('contentType').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|     }) | ||||
|     // PAGE LINKS -------------------------- | ||||
|     .createTable('pageLinks', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.string('localeCode', 5).notNullable() | ||||
|     }) | ||||
|     // PAGES ------------------------------- | ||||
|     .createTable('pages', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.string('hash').notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.string('description') | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isPublished').notNullable().defaultTo(false) | ||||
|       table.string('privateNS') | ||||
|       table.string('publishStartDate') | ||||
|       table.string('publishEndDate') | ||||
|       switch (WIKI.config.db.type) { | ||||
|         case 'postgres': | ||||
|         case 'sqlite': | ||||
|           table.text('content') | ||||
|           table.text('render') | ||||
|           break | ||||
|         case 'mariadb': | ||||
|         case 'mysql': | ||||
|           table.specificType('content', 'LONGTEXT') | ||||
|           table.specificType('render', 'LONGTEXT') | ||||
|           break | ||||
|         case 'mssql': | ||||
|           table.specificType('content', 'VARCHAR(max)') | ||||
|           table.specificType('render', 'VARCHAR(max)') | ||||
|           break | ||||
|       } | ||||
|       table.json('toc') | ||||
|       table.string('contentType').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // PAGE TREE --------------------------- | ||||
|     .createTable('pageTree', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.integer('id').unsigned().primary() | ||||
|       table.string('path').notNullable() | ||||
|       table.integer('depth').unsigned().notNullable() | ||||
|       table.string('title').notNullable() | ||||
|       table.boolean('isPrivate').notNullable().defaultTo(false) | ||||
|       table.boolean('isFolder').notNullable().defaultTo(false) | ||||
|       table.string('privateNS') | ||||
|     }) | ||||
|     // RENDERERS --------------------------- | ||||
|     .createTable('renderers', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // SEARCH ------------------------------ | ||||
|     .createTable('searchEngines', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.json('config') | ||||
|     }) | ||||
|     // SETTINGS ---------------------------- | ||||
|     .createTable('settings', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.json('value') | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // STORAGE ----------------------------- | ||||
|     .createTable('storage', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.string('key').notNullable().primary() | ||||
|       table.boolean('isEnabled').notNullable().defaultTo(false) | ||||
|       table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push') | ||||
|       table.json('config') | ||||
|       table.string('syncInterval') | ||||
|       table.json('state') | ||||
|     }) | ||||
|     // TAGS -------------------------------- | ||||
|     .createTable('tags', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('tag').notNullable().unique() | ||||
|       table.string('title') | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // USER KEYS --------------------------- | ||||
|     .createTable('userKeys', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('kind').notNullable() | ||||
|       table.string('token').notNullable() | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('validUntil').notNullable() | ||||
|     }) | ||||
|     // USERS ------------------------------- | ||||
|     .createTable('users', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.string('email').notNullable() | ||||
|       table.string('name').notNullable() | ||||
|       table.string('providerId') | ||||
|       table.string('password') | ||||
|       table.boolean('tfaIsActive').notNullable().defaultTo(false) | ||||
|       table.string('tfaSecret') | ||||
|       table.string('jobTitle').defaultTo('') | ||||
|       table.string('location').defaultTo('') | ||||
|       table.string('pictureUrl') | ||||
|       table.string('timezone').notNullable().defaultTo('America/New_York') | ||||
|       table.boolean('isSystem').notNullable().defaultTo(false) | ||||
|       table.boolean('isActive').notNullable().defaultTo(false) | ||||
|       table.boolean('isVerified').notNullable().defaultTo(false) | ||||
|       table.boolean('mustChangePwd').notNullable().defaultTo(false) | ||||
|       table.string('createdAt').notNullable() | ||||
|       table.string('updatedAt').notNullable() | ||||
|     }) | ||||
|     // ===================================== | ||||
|     // RELATION TABLES | ||||
|     // ===================================== | ||||
|     // PAGE HISTORY TAGS --------------------------- | ||||
|     .createTable('pageHistoryTags', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE') | ||||
|       table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') | ||||
|     }) | ||||
|     // PAGE TAGS --------------------------- | ||||
|     .createTable('pageTags', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') | ||||
|     }) | ||||
|     // USER GROUPS ------------------------- | ||||
|     .createTable('userGroups', table => { | ||||
|       if (dbCompat.charset) { table.charset('utf8mb4') } | ||||
|       table.increments('id').primary() | ||||
|       table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE') | ||||
|       table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE') | ||||
|     }) | ||||
|     // ===================================== | ||||
|     // REFERENCES | ||||
|     // ===================================== | ||||
|     .table('assets', table => { | ||||
|       table.integer('folderId').unsigned().references('id').inTable('assetFolders') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('comments', table => { | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('pageHistory', table => { | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('pageLinks', table => { | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.index(['path', 'localeCode']) | ||||
|     }) | ||||
|     .table('pages', table => { | ||||
|       table.string('editorKey').references('key').inTable('editors') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|       table.integer('authorId').unsigned().references('id').inTable('users') | ||||
|       table.integer('creatorId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('pageTree', table => { | ||||
|       if (dbCompat.selfCascadeDelete) { | ||||
|         table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE') | ||||
|       } else { | ||||
|         table.integer('parent').unsigned() | ||||
|       } | ||||
|       table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales') | ||||
|     }) | ||||
|     .table('userKeys', table => { | ||||
|       table.integer('userId').unsigned().references('id').inTable('users') | ||||
|     }) | ||||
|     .table('users', table => { | ||||
|       table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local') | ||||
|       table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en') | ||||
|       table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown') | ||||
|  | ||||
|       table.unique(['providerKey', 'email']) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.down = knex => { } | ||||
| @@ -13,7 +13,7 @@ const he = require('he') | ||||
|  | ||||
| const frontmatterRegex = { | ||||
|   html: /^(<!-{2}(?:\n|\r)([\w\W]+?)(?:\n|\r)-{2}>)?(?:\n|\r)*([\w\W]*)*/, | ||||
|   legacy: /^(<!-- TITLE: ?([\w\W]+?) -{2}>)?(?:\n|\r)?(<!-- SUBTITLE: ?([\w\W]+?) -{2}>)?(?:\n|\r)*([\w\W]*)*/i, | ||||
|   legacy: /^(<!-- TITLE: ?([\w\W]+?) ?-{2}>)?(?:\n|\r)?(<!-- SUBTITLE: ?([\w\W]+?) ?-{2}>)?(?:\n|\r)*([\w\W]*)*/i, | ||||
|   markdown: /^(-{3}(?:\n|\r)([\w\W]+?)(?:\n|\r)-{3})?(?:\n|\r)*([\w\W]*)*/ | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user