@ -1,115 +0,0 @@
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
// =====================================
|
||||
// MODEL TABLES
|
||||
// =====================================
|
||||
// ASSETS ------------------------------
|
||||
.createTable('assets', table => {
|
||||
table.increments('id').primary()
|
||||
table.string('filename').notNullable()
|
||||
table.string('basename').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()
|
||||
|
||||
table.integer('folderId').unsigned().references('id').inTable('assetFolders')
|
||||
table.integer('authorId').unsigned().references('id').inTable('users')
|
||||
})
|
||||
// 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.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.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
table.string('editorKey').references('key').inTable('editors')
|
||||
table.string('localeCode', 5).references('code').inTable('locales')
|
||||
table.integer('authorId').unsigned().references('id').inTable('users')
|
||||
})
|
||||
// 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.increments('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')
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
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')
|
||||
})
|
||||
// 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.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'])
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('userGroups')
|
||||
.dropTableIfExists('pageHistoryTags')
|
||||
.dropTableIfExists('pageHistory')
|
||||
.dropTableIfExists('pageTags')
|
||||
.dropTableIfExists('assets')
|
||||
.dropTableIfExists('assetFolders')
|
||||
.dropTableIfExists('comments')
|
||||
.dropTableIfExists('editors')
|
||||
.dropTableIfExists('groups')
|
||||
.dropTableIfExists('locales')
|
||||
.dropTableIfExists('navigation')
|
||||
.dropTableIfExists('pages')
|
||||
.dropTableIfExists('renderers')
|
||||
.dropTableIfExists('settings')
|
||||
.dropTableIfExists('storage')
|
||||
.dropTableIfExists('tags')
|
||||
.dropTableIfExists('userKeys')
|
||||
.dropTableIfExists('users')
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.renameTable('pageHistory', 'pageHistory_old')
|
||||
.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')
|
||||
})
|
||||
.raw(`INSERT INTO pageHistory SELECT id,path,hash,title,description,isPrivate,isPublished,publishStartDate,publishEndDate,content,contentType,createdAt,'updated' AS action,pageId,editorKey,localeCode,authorId FROM pageHistory_old;`)
|
||||
.dropTable('pageHistory_old')
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.renameTable('pageHistory', 'pageHistory_old')
|
||||
.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.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
table.string('editorKey').references('key').inTable('editors')
|
||||
table.string('localeCode', 5).references('code').inTable('locales')
|
||||
table.integer('authorId').unsigned().references('id').inTable('users')
|
||||
})
|
||||
.raw('INSERT INTO pageHistory SELECT id,path,hash,title,description,isPrivate,isPublished,publishStartDate,publishEndDate,content,contentType,createdAt,NULL as pageId,editorKey,localeCode,authorId FROM pageHistory_old;')
|
||||
.dropTable('pageHistory_old')
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('assets', table => {
|
||||
table.dropColumn('basename')
|
||||
table.string('hash').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('assets', table => {
|
||||
table.dropColumn('hash')
|
||||
table.string('basename').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('analytics', table => {
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('analytics')
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('locales', table => {
|
||||
table.integer('availability').notNullable().defaultTo(0)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('locales', table => {
|
||||
table.dropColumn('availability')
|
||||
})
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('users', table => {
|
||||
table.boolean('mustChangePwd').notNullable().defaultTo(false)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('users', table => {
|
||||
table.dropColumn('mustChangePwd')
|
||||
})
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.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()
|
||||
})
|
||||
.table('pageLinks', table => {
|
||||
table.index(['path', 'localeCode'])
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('pageLinks')
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('storage', table => {
|
||||
table.string('syncInterval')
|
||||
table.json('state')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('storage', table => {
|
||||
table.dropColumn('syncInterval')
|
||||
table.dropColumn('state')
|
||||
})
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('assetData', table => {
|
||||
table.integer('id').primary()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('assetData')
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.dropTable('pageTree')
|
||||
.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')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTable('pageTree')
|
||||
.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')
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
table.string('localeCode', 5).references('code').inTable('locales')
|
||||
})
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
// =====================================
|
||||
// MODEL TABLES
|
||||
// =====================================
|
||||
// ASSETS ------------------------------
|
||||
.createTable('assets', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('filename').notNullable()
|
||||
table.string('basename').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 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', 2).notNullable().primary()
|
||||
table.json('strings')
|
||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||
table.string('name').notNullable()
|
||||
table.string('nativeName').notNullable()
|
||||
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.text('content')
|
||||
table.string('contentType').notNullable()
|
||||
table.string('createdAt').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')
|
||||
table.text('content')
|
||||
table.text('render')
|
||||
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.increments('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')
|
||||
})
|
||||
// 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')
|
||||
})
|
||||
// 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.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.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
table.string('editorKey').references('key').inTable('editors')
|
||||
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', 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', 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', 2).references('code').inTable('locales').notNullable().defaultTo('en')
|
||||
table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
|
||||
|
||||
table.unique(['providerKey', 'email'])
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('userGroups')
|
||||
.dropTableIfExists('pageHistoryTags')
|
||||
.dropTableIfExists('pageHistory')
|
||||
.dropTableIfExists('pageTags')
|
||||
.dropTableIfExists('assets')
|
||||
.dropTableIfExists('assetFolders')
|
||||
.dropTableIfExists('comments')
|
||||
.dropTableIfExists('editors')
|
||||
.dropTableIfExists('groups')
|
||||
.dropTableIfExists('locales')
|
||||
.dropTableIfExists('navigation')
|
||||
.dropTableIfExists('pages')
|
||||
.dropTableIfExists('renderers')
|
||||
.dropTableIfExists('settings')
|
||||
.dropTableIfExists('storage')
|
||||
.dropTableIfExists('tags')
|
||||
.dropTableIfExists('userKeys')
|
||||
.dropTableIfExists('users')
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('pageHistory', table => {
|
||||
table.string('action').defaultTo('updated')
|
||||
table.dropForeign('pageId')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('pageHistory', table => {
|
||||
table.dropColumn('action')
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
})
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('assets', table => {
|
||||
table.dropColumn('basename')
|
||||
table.string('hash').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('assets', table => {
|
||||
table.dropColumn('hash')
|
||||
table.string('basename').notNullable()
|
||||
})
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.table('assetData', table => {
|
||||
if (dbCompat.blobLength) {
|
||||
table.dropColumn('data')
|
||||
}
|
||||
})
|
||||
.table('assetData', table => {
|
||||
if (dbCompat.blobLength) {
|
||||
table.specificType('data', 'LONGBLOB').notNullable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('assetData', table => {})
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('analytics', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('analytics')
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('locales', table => {
|
||||
table.integer('availability').notNullable().defaultTo(0)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('locales', table => {
|
||||
table.dropColumn('availability')
|
||||
})
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('users', table => {
|
||||
table.boolean('mustChangePwd').notNullable().defaultTo(false)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('users', table => {
|
||||
table.dropColumn('mustChangePwd')
|
||||
})
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('pageLinks', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
||||
table.string('path').notNullable()
|
||||
table.string('localeCode', 5).notNullable()
|
||||
})
|
||||
.table('pageLinks', table => {
|
||||
table.index(['path', 'localeCode'])
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('pageLinks')
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.table('storage', table => {
|
||||
table.string('syncInterval')
|
||||
table.json('state')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.table('storage', table => {
|
||||
table.dropColumn('syncInterval')
|
||||
table.dropColumn('state')
|
||||
})
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('assetData', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.integer('id').primary()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('assetData')
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
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 => {
|
||||
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')
|
||||
})
|
||||
.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')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = 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 => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
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('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')
|
||||
})
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/* 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 => { }
|
@ -0,0 +1,32 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
async migrate (knex) {
|
||||
const migrationsTableExists = await knex.schema.hasTable('migrations')
|
||||
if (!migrationsTableExists) {
|
||||
return
|
||||
}
|
||||
|
||||
const migrations = await knex('migrations')
|
||||
if (_.some(migrations, m => m.name.indexOf('2.5.128') >= 0)) {
|
||||
// TODO: 2.x MIGRATIONS for 3.0
|
||||
WIKI.logger.error('Upgrading from 2.x is not yet supported. A future release will allow for upgrade from 2.x. Exiting...')
|
||||
process.exit(1)
|
||||
|
||||
// -> Cleanup migration table
|
||||
await knex('migrations').truncate()
|
||||
|
||||
// -> Advance to stable 3.0 migration state
|
||||
await knex('migrations').insert({
|
||||
name: '3.0.0.js',
|
||||
batch: 1,
|
||||
migration_time: knex.fn.now()
|
||||
})
|
||||
} else {
|
||||
console.error('CANNOT UPGRADE FROM OLDER UNSUPPORTED VERSION. UPGRADE TO THE LATEST 2.X VERSION FIRST! Exiting...')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
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 => { }
|
@ -1,9 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pageHistory', table => {
|
||||
table.string('versionDate').notNullable().defaultTo('')
|
||||
})
|
||||
.raw(`UPDATE pageHistory AS h1 SET versionDate = COALESCE((SELECT createdAt FROM pageHistory AS h2 WHERE h2.pageId = h1.pageId AND h2.id < h1.id ORDER BY h2.id DESC LIMIT 1), h1.createdAt, '')`)
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,14 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('apiKeys', table => {
|
||||
table.increments('id').primary()
|
||||
table.string('name').notNullable()
|
||||
table.text('key').notNullable()
|
||||
table.string('expiration').notNullable()
|
||||
table.boolean('isRevoked').notNullable().defaultTo(false)
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('users', table => {
|
||||
table.string('lastLoginAt')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,10 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('commentProviders', table => {
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pageTree', table => {
|
||||
table.json('ancestors')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,15 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pages', table => {
|
||||
table.json('extra').notNullable().defaultTo('{}')
|
||||
})
|
||||
.alterTable('pageHistory', table => {
|
||||
table.json('extra').notNullable().defaultTo('{}')
|
||||
})
|
||||
.alterTable('users', table => {
|
||||
table.string('dateFormat').notNullable().defaultTo('')
|
||||
table.string('appearance').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,11 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('comments', table => {
|
||||
table.text('render').notNullable().defaultTo('')
|
||||
table.string('name').notNullable().defaultTo('')
|
||||
table.string('email').notNullable().defaultTo('')
|
||||
table.string('ip').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('comments', table => {
|
||||
table.integer('replyTo').unsigned().notNullable().defaultTo(0)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,34 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
// Check for users using disabled strategies
|
||||
let protectedStrategies = []
|
||||
const disabledStrategies = await knex('authentication').where('isEnabled', false)
|
||||
if (disabledStrategies) {
|
||||
const incompatibleUsers = await knex('users').distinct('providerKey').whereIn('providerKey', disabledStrategies.map(s => s.key))
|
||||
if (incompatibleUsers && incompatibleUsers.length > 0) {
|
||||
protectedStrategies = incompatibleUsers.map(u => u.providerKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete disabled strategies
|
||||
await knex('authentication').whereNotIn('key', protectedStrategies).andWhere('isEnabled', false).del()
|
||||
|
||||
// Update table schema
|
||||
await knex.schema
|
||||
.alterTable('authentication', table => {
|
||||
table.integer('order').unsigned().notNullable().defaultTo(0)
|
||||
table.string('strategyKey').notNullable().defaultTo('')
|
||||
table.string('displayName').notNullable().defaultTo('')
|
||||
})
|
||||
|
||||
// Fix pre-2.5 strategies
|
||||
const strategies = await knex('authentication')
|
||||
let idx = 1
|
||||
for (const strategy of strategies) {
|
||||
await knex('authentication').where('key', strategy.key).update({
|
||||
strategyKey: strategy.key,
|
||||
order: (strategy.key === 'local') ? 0 : idx++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,14 +0,0 @@
|
||||
const has = require('lodash/has')
|
||||
|
||||
exports.up = async knex => {
|
||||
// -> Fix 2.5.1 added isEnabled columns for beta users
|
||||
const localStrategy = await knex('authentication').where('key', 'local').first()
|
||||
if (localStrategy && !has(localStrategy, 'isEnabled')) {
|
||||
await knex.schema
|
||||
.alterTable('authentication', table => {
|
||||
table.boolean('isEnabled').notNullable().defaultTo(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,6 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
// -> Fix 2.5.117 new installations without isEnabled on local auth (#2382)
|
||||
await knex('authentication').where('key', 'local').update({ isEnabled: true })
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
await knex.schema
|
||||
.alterTable('groups', table => {
|
||||
table.string('redirectOnLogin').notNullable().defaultTo('/')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,9 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('userAvatars', table => {
|
||||
table.integer('id').primary()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,7 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
await knex('users').update({
|
||||
email: knex.raw('LOWER(email)')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,325 +0,0 @@
|
||||
/* 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 => { }
|
@ -1,19 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pageHistory', table => {
|
||||
switch (WIKI.config.db.type) {
|
||||
// No change needed for PostgreSQL and SQLite
|
||||
case 'mariadb':
|
||||
case 'mysql':
|
||||
table.specificType('content', 'LONGTEXT').alter()
|
||||
break
|
||||
case 'mssql':
|
||||
table.specificType('content', 'VARCHAR(max)').alter()
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,37 +0,0 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = async knex => {
|
||||
let sqlVersionDate = ''
|
||||
switch (WIKI.config.db.type) {
|
||||
case 'postgres':
|
||||
sqlVersionDate = 'UPDATE "pageHistory" h1 SET "versionDate" = COALESCE((SELECT prev."createdAt" FROM "pageHistory" prev WHERE prev."pageId" = h1."pageId" AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1), h1."createdAt")'
|
||||
break
|
||||
case 'mssql':
|
||||
sqlVersionDate = 'UPDATE h1 SET "versionDate" = COALESCE((SELECT TOP 1 prev."createdAt" FROM "pageHistory" prev WHERE prev."pageId" = h1."pageId" AND prev.id < h1.id ORDER BY prev.id DESC), h1."createdAt") FROM "pageHistory" h1'
|
||||
break
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
// -> Fix for 2.2.50 failed migration
|
||||
const pageHistoryColumns = await knex.schema.raw('SHOW COLUMNS FROM pageHistory')
|
||||
if (_.some(pageHistoryColumns[0], ['Field', 'versionDate'])) {
|
||||
console.info('MySQL 2.2.50 Migration Fix - Dropping failed versionDate column...')
|
||||
await knex.schema.raw('ALTER TABLE pageHistory DROP COLUMN versionDate')
|
||||
console.info('versionDate column dropped successfully.')
|
||||
}
|
||||
|
||||
sqlVersionDate = `UPDATE pageHistory AS h1 INNER JOIN pageHistory AS h2 ON h2.id = (SELECT prev.id FROM (SELECT * FROM pageHistory) AS prev WHERE prev.pageId = h1.pageId AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1) SET h1.versionDate = h2.createdAt`
|
||||
break
|
||||
// case 'mariadb':
|
||||
// sqlVersionDate = `UPDATE pageHistory AS h1 INNER JOIN pageHistory AS h2 ON h2.id = (SELECT prev.id FROM pageHistory AS prev WHERE prev.pageId = h1.pageId AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1) SET h1.versionDate = h2.createdAt`
|
||||
// break
|
||||
}
|
||||
await knex.schema
|
||||
.alterTable('pageHistory', table => {
|
||||
table.string('versionDate').notNullable().defaultTo('')
|
||||
})
|
||||
.raw(sqlVersionDate)
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,20 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('apiKeys', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('name').notNullable()
|
||||
table.text('key').notNullable()
|
||||
table.string('expiration').notNullable()
|
||||
table.boolean('isRevoked').notNullable().defaultTo(false)
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('users', table => {
|
||||
table.string('lastLoginAt')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pageTree', table => {
|
||||
table.json('ancestors')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,25 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('pages', table => {
|
||||
if (WIKI.config.db.type === 'mysql') {
|
||||
table.json('extra')
|
||||
} else {
|
||||
table.json('extra').notNullable().defaultTo('{}')
|
||||
}
|
||||
})
|
||||
.alterTable('pageHistory', table => {
|
||||
if (WIKI.config.db.type === 'mysql') {
|
||||
table.json('extra')
|
||||
} else {
|
||||
table.json('extra').notNullable().defaultTo('{}')
|
||||
}
|
||||
})
|
||||
.alterTable('users', table => {
|
||||
table.string('dateFormat').notNullable().defaultTo('')
|
||||
table.string('appearance').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,16 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('commentProviders', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,11 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('comments', table => {
|
||||
table.text('render').notNullable().defaultTo('')
|
||||
table.string('name').notNullable().defaultTo('')
|
||||
table.string('email').notNullable().defaultTo('')
|
||||
table.string('ip').notNullable().defaultTo('')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.alterTable('comments', table => {
|
||||
table.integer('replyTo').unsigned().notNullable().defaultTo(0)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,34 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
// Check for users using disabled strategies
|
||||
let protectedStrategies = []
|
||||
const disabledStrategies = await knex('authentication').where('isEnabled', false)
|
||||
if (disabledStrategies) {
|
||||
const incompatibleUsers = await knex('users').distinct('providerKey').whereIn('providerKey', disabledStrategies.map(s => s.key))
|
||||
if (incompatibleUsers && incompatibleUsers.length > 0) {
|
||||
protectedStrategies = incompatibleUsers.map(u => u.providerKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete disabled strategies
|
||||
await knex('authentication').whereNotIn('key', protectedStrategies).andWhere('isEnabled', false).del()
|
||||
|
||||
// Update table schema
|
||||
await knex.schema
|
||||
.alterTable('authentication', table => {
|
||||
table.integer('order').unsigned().notNullable().defaultTo(0)
|
||||
table.string('strategyKey').notNullable().defaultTo('')
|
||||
table.string('displayName').notNullable().defaultTo('')
|
||||
})
|
||||
|
||||
// Fix pre-2.5 strategies
|
||||
const strategies = await knex('authentication')
|
||||
let idx = 1
|
||||
for (const strategy of strategies) {
|
||||
await knex('authentication').where('key', strategy.key).update({
|
||||
strategyKey: strategy.key,
|
||||
order: (strategy.key === 'local') ? 0 : idx++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,14 +0,0 @@
|
||||
const has = require('lodash/has')
|
||||
|
||||
exports.up = async knex => {
|
||||
// -> Fix 2.5.1 added isEnabled columns for beta users
|
||||
const localStrategy = await knex('authentication').where('key', 'local').first()
|
||||
if (localStrategy && !has(localStrategy, 'isEnabled')) {
|
||||
await knex.schema
|
||||
.alterTable('authentication', table => {
|
||||
table.boolean('isEnabled').notNullable().defaultTo(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,6 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
// -> Fix 2.5.117 new installations without isEnabled on local auth (#2382)
|
||||
await knex('authentication').where('key', 'local').update({ isEnabled: true })
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,8 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
await knex.schema
|
||||
.alterTable('groups', table => {
|
||||
table.string('redirectOnLogin').notNullable().defaultTo('/')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,20 +0,0 @@
|
||||
/* 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`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('userAvatars', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.integer('id').primary()
|
||||
if (dbCompat.blobLength) {
|
||||
table.specificType('data', 'LONGBLOB').notNullable()
|
||||
} else {
|
||||
table.binary('data').notNullable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,7 +0,0 @@
|
||||
exports.up = async knex => {
|
||||
await knex('users').update({
|
||||
email: knex.raw('LOWER(??)', ['email'])
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -0,0 +1,589 @@
|
||||
const { v4: uuid } = require('uuid')
|
||||
const bcrypt = require('bcryptjs-then')
|
||||
const crypto = require('crypto')
|
||||
const pem2jwk = require('pem-jwk').pem2jwk
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = async knex => {
|
||||
WIKI.logger.info('Running 3.0.0 database migration...')
|
||||
|
||||
// =====================================
|
||||
// PG EXTENSIONS
|
||||
// =====================================
|
||||
await knex.raw('CREATE EXTENSION IF NOT EXISTS pgcrypto;')
|
||||
|
||||
await knex.schema
|
||||
// =====================================
|
||||
// MODEL TABLES
|
||||
// =====================================
|
||||
// ANALYTICS ---------------------------
|
||||
.createTable('analytics', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('module').notNullable()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.jsonb('config').notNullable()
|
||||
})
|
||||
// API KEYS ----------------------------
|
||||
.createTable('apiKeys', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('name').notNullable()
|
||||
table.text('key').notNullable()
|
||||
table.string('expiration').notNullable()
|
||||
table.boolean('isRevoked').notNullable().defaultTo(false)
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// ASSETS ------------------------------
|
||||
.createTable('assets', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('filename').notNullable()
|
||||
table.string('hash').notNullable().index()
|
||||
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.jsonb('metadata')
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// ASSET DATA --------------------------
|
||||
.createTable('assetData', table => {
|
||||
table.uuid('id').notNullable().index()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
// ASSET FOLDERS -----------------------
|
||||
.createTable('assetFolders', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('name').notNullable()
|
||||
table.string('slug').notNullable()
|
||||
})
|
||||
// AUTHENTICATION ----------------------
|
||||
.createTable('authentication', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('module').notNullable()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.integer('order').unsigned().notNullable().defaultTo(0)
|
||||
table.string('displayName').notNullable().defaultTo('')
|
||||
table.jsonb('config').notNullable().defaultTo('{}')
|
||||
table.boolean('selfRegistration').notNullable().defaultTo(false)
|
||||
table.jsonb('domainWhitelist').notNullable().defaultTo('[]')
|
||||
table.jsonb('autoEnrollGroups').notNullable().defaultTo('[]')
|
||||
table.jsonb('hideOnSites').notNullable().defaultTo('[]')
|
||||
})
|
||||
// COMMENTS ----------------------------
|
||||
.createTable('comments', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.uuid('replyTo')
|
||||
table.text('content').notNullable()
|
||||
table.text('render').notNullable().defaultTo('')
|
||||
table.string('name').notNullable().defaultTo('')
|
||||
table.string('email').notNullable().defaultTo('')
|
||||
table.string('ip').notNullable().defaultTo('')
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// GROUPS ------------------------------
|
||||
.createTable('groups', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('name').notNullable()
|
||||
table.jsonb('permissions').notNullable()
|
||||
table.jsonb('rules').notNullable()
|
||||
table.string('redirectOnLogin').notNullable().defaultTo('')
|
||||
table.string('redirectOnFirstLogin').notNullable().defaultTo('')
|
||||
table.string('redirectOnLogout').notNullable().defaultTo('')
|
||||
table.boolean('isSystem').notNullable().defaultTo(false)
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// HOOKS -------------------------------
|
||||
.createTable('hooks', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('name').notNullable()
|
||||
table.jsonb('events').notNullable().defaultTo('[]')
|
||||
table.string('url').notNullable()
|
||||
table.boolean('includeMetadata').notNullable().defaultTo(false)
|
||||
table.boolean('includeContent').notNullable().defaultTo(false)
|
||||
table.boolean('acceptUntrusted').notNullable().defaultTo(false)
|
||||
table.string('authHeader')
|
||||
table.enum('state', ['pending', 'error', 'success']).notNullable().defaultTo('pending')
|
||||
table.string('lastErrorMessage')
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// LOCALES -----------------------------
|
||||
.createTable('locales', table => {
|
||||
table.string('code', 5).notNullable().primary()
|
||||
table.jsonb('strings')
|
||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||
table.string('name').notNullable()
|
||||
table.string('nativeName').notNullable()
|
||||
table.integer('availability').notNullable().defaultTo(0)
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// NAVIGATION ----------------------------
|
||||
.createTable('navigation', table => {
|
||||
table.string('key').notNullable().primary()
|
||||
table.jsonb('config')
|
||||
})
|
||||
// PAGE HISTORY ------------------------
|
||||
.createTable('pageHistory', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.uuid('pageId').notNullable().index()
|
||||
table.string('path').notNullable()
|
||||
table.string('hash').notNullable()
|
||||
table.string('title').notNullable()
|
||||
table.string('description')
|
||||
table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
|
||||
table.timestamp('publishStartDate')
|
||||
table.timestamp('publishEndDate')
|
||||
table.string('action').defaultTo('updated')
|
||||
table.text('content')
|
||||
table.string('contentType').notNullable()
|
||||
table.jsonb('extra').notNullable().defaultTo('{}')
|
||||
table.jsonb('tags').defaultTo('[]')
|
||||
table.timestamp('versionDate').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// PAGE LINKS --------------------------
|
||||
.createTable('pageLinks', table => {
|
||||
table.increments('id').primary()
|
||||
table.string('path').notNullable()
|
||||
table.string('localeCode', 5).notNullable()
|
||||
})
|
||||
// PAGES -------------------------------
|
||||
.createTable('pages', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('slug')
|
||||
table.string('path').notNullable()
|
||||
table.string('hash').notNullable()
|
||||
table.string('title').notNullable()
|
||||
table.string('description')
|
||||
table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
|
||||
table.timestamp('publishStartDate')
|
||||
table.timestamp('publishEndDate')
|
||||
table.text('content')
|
||||
table.text('render')
|
||||
table.jsonb('toc')
|
||||
table.string('contentType').notNullable()
|
||||
table.jsonb('extra').notNullable().defaultTo('{}')
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// PAGE TREE ---------------------------
|
||||
.createTable('pageTree', table => {
|
||||
table.integer('id').unsigned().primary()
|
||||
table.string('path').notNullable()
|
||||
table.integer('depth').unsigned().notNullable()
|
||||
table.string('title').notNullable()
|
||||
table.boolean('isFolder').notNullable().defaultTo(false)
|
||||
table.jsonb('ancestors')
|
||||
})
|
||||
// RENDERERS ---------------------------
|
||||
.createTable('renderers', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('module').notNullable()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.jsonb('config')
|
||||
})
|
||||
// SETTINGS ----------------------------
|
||||
.createTable('settings', table => {
|
||||
table.string('key').notNullable().primary()
|
||||
table.jsonb('value')
|
||||
})
|
||||
// SITES -------------------------------
|
||||
.createTable('sites', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('hostname').notNullable()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.jsonb('config').notNullable()
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// STORAGE -----------------------------
|
||||
.createTable('storage', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('module').notNullable()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.jsonb('contentTypes')
|
||||
table.jsonb('assetDelivery')
|
||||
table.jsonb('versioning')
|
||||
table.jsonb('schedule')
|
||||
table.jsonb('config')
|
||||
table.jsonb('state')
|
||||
})
|
||||
// TAGS --------------------------------
|
||||
.createTable('tags', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('tag').notNullable()
|
||||
table.jsonb('display').notNullable().defaultTo('{}')
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// USER AVATARS ------------------------
|
||||
.createTable('userAvatars', table => {
|
||||
table.uuid('id').notNullable().primary()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
// USER KEYS ---------------------------
|
||||
.createTable('userKeys', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('kind').notNullable()
|
||||
table.string('token').notNullable()
|
||||
table.timestamp('validUntil').notNullable()
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// USERS -------------------------------
|
||||
.createTable('users', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('email').notNullable()
|
||||
table.string('name').notNullable()
|
||||
table.jsonb('auth')
|
||||
table.jsonb('tfa')
|
||||
table.jsonb('meta')
|
||||
table.jsonb('prefs')
|
||||
table.string('pictureUrl')
|
||||
table.boolean('isSystem').notNullable().defaultTo(false)
|
||||
table.boolean('isActive').notNullable().defaultTo(false)
|
||||
table.boolean('isVerified').notNullable().defaultTo(false)
|
||||
table.timestamp('lastLoginAt').index()
|
||||
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
|
||||
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
|
||||
})
|
||||
// =====================================
|
||||
// RELATION TABLES
|
||||
// =====================================
|
||||
// PAGE TAGS ---------------------------
|
||||
.createTable('pageTags', table => {
|
||||
table.increments('id').primary()
|
||||
table.uuid('pageId').references('id').inTable('pages').onDelete('CASCADE')
|
||||
table.uuid('tagId').references('id').inTable('tags').onDelete('CASCADE')
|
||||
})
|
||||
// USER GROUPS -------------------------
|
||||
.createTable('userGroups', table => {
|
||||
table.increments('id').primary()
|
||||
table.uuid('userId').references('id').inTable('users').onDelete('CASCADE')
|
||||
table.uuid('groupId').references('id').inTable('groups').onDelete('CASCADE')
|
||||
})
|
||||
// =====================================
|
||||
// REFERENCES
|
||||
// =====================================
|
||||
.table('analytics', table => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites')
|
||||
})
|
||||
.table('assets', table => {
|
||||
table.uuid('folderId').notNullable().references('id').inTable('assetFolders').index()
|
||||
table.uuid('authorId').notNullable().references('id').inTable('users')
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
|
||||
})
|
||||
.table('assetFolders', table => {
|
||||
table.uuid('parentId').references('id').inTable('assetFolders').index()
|
||||
})
|
||||
.table('comments', table => {
|
||||
table.uuid('pageId').notNullable().references('id').inTable('pages').index()
|
||||
table.uuid('authorId').notNullable().references('id').inTable('users').index()
|
||||
})
|
||||
.table('navigation', table => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
|
||||
})
|
||||
.table('pageHistory', table => {
|
||||
table.string('localeCode', 5).references('code').inTable('locales')
|
||||
table.uuid('authorId').notNullable().references('id').inTable('users')
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
|
||||
})
|
||||
.table('pageLinks', table => {
|
||||
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
|
||||
table.index(['path', 'localeCode'])
|
||||
})
|
||||
.table('pages', table => {
|
||||
table.string('localeCode', 5).references('code').inTable('locales').index()
|
||||
table.uuid('authorId').notNullable().references('id').inTable('users').index()
|
||||
table.uuid('creatorId').notNullable().references('id').inTable('users').index()
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
|
||||
})
|
||||
.table('pageTree', table => {
|
||||
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
|
||||
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
|
||||
table.string('localeCode', 5).references('code').inTable('locales')
|
||||
})
|
||||
.table('storage', table => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites')
|
||||
})
|
||||
.table('tags', table => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites')
|
||||
table.unique(['siteId', 'tag'])
|
||||
})
|
||||
.table('userKeys', table => {
|
||||
table.uuid('userId').notNullable().references('id').inTable('users')
|
||||
})
|
||||
.table('users', table => {
|
||||
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
|
||||
})
|
||||
|
||||
// =====================================
|
||||
// DEFAULT DATA
|
||||
// =====================================
|
||||
|
||||
// -> SYSTEM CONFIG
|
||||
|
||||
await knex('settings').insert([
|
||||
{
|
||||
key: 'update',
|
||||
value: {
|
||||
locales: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'mail',
|
||||
value: {
|
||||
senderName: '',
|
||||
senderEmail: '',
|
||||
host: '',
|
||||
port: 465,
|
||||
secure: true,
|
||||
verifySSL: true,
|
||||
user: '',
|
||||
pass: '',
|
||||
useDKIM: false,
|
||||
dkimDomainName: '',
|
||||
dkimKeySelector: '',
|
||||
dkimPrivateKey: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'security',
|
||||
value: {
|
||||
corsConfig: '',
|
||||
corsMode: 'OFF',
|
||||
cspDirectives: '',
|
||||
disallowFloc: true,
|
||||
disallowIframe: true,
|
||||
disallowOpenRedirect: true,
|
||||
enforceCsp: false,
|
||||
enforceHsts: false,
|
||||
enforceSameOriginReferrerPolicy: true,
|
||||
forceAssetDownload: true,
|
||||
hstsDuration: 0,
|
||||
trustProxy: false,
|
||||
authJwtAudience: 'urn:wiki.js',
|
||||
authJwtExpiration: '30m',
|
||||
authJwtRenewablePeriod: '14d',
|
||||
uploadMaxFileSize: 10485760,
|
||||
uploadMaxFiles: 20,
|
||||
uploadScanSVG: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// -> DEFAULT LOCALE
|
||||
|
||||
await knex('locales').insert({
|
||||
code: 'en',
|
||||
strings: {},
|
||||
isRTL: false,
|
||||
name: 'English',
|
||||
nativeName: 'English'
|
||||
})
|
||||
|
||||
// -> DEFAULT SITE
|
||||
|
||||
WIKI.logger.info('Generating certificates...')
|
||||
const secret = crypto.randomBytes(32).toString('hex')
|
||||
const certs = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: secret
|
||||
}
|
||||
})
|
||||
|
||||
const siteId = uuid()
|
||||
await knex('sites').insert({
|
||||
id: siteId,
|
||||
hostname: '*',
|
||||
isEnabled: true,
|
||||
config: {
|
||||
auth: {
|
||||
audience: 'urn:wiki.js',
|
||||
tokenExpiration: '30m',
|
||||
tokenRenewal: '14d',
|
||||
certs: {
|
||||
jwk: pem2jwk(certs.publicKey),
|
||||
public: certs.publicKey,
|
||||
private: certs.privateKey
|
||||
},
|
||||
secret
|
||||
},
|
||||
title: 'My Wiki Site',
|
||||
description: '',
|
||||
company: '',
|
||||
contentLicense: '',
|
||||
defaults: {
|
||||
timezone: 'America/New_York',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: '12h'
|
||||
},
|
||||
features: {
|
||||
ratings: false,
|
||||
ratingsMode: 'off',
|
||||
comments: false,
|
||||
contributions: false,
|
||||
profile: true,
|
||||
search: true
|
||||
},
|
||||
logoText: true,
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true
|
||||
},
|
||||
locale: 'en',
|
||||
localeNamespacing: false,
|
||||
localeNamespaces: [],
|
||||
theme: {
|
||||
dark: false,
|
||||
colorPrimary: '#1976d2',
|
||||
colorSecondary: '#02c39a',
|
||||
colorAccent: '#f03a47',
|
||||
colorHeader: '#000000',
|
||||
colorSidebar: '#1976d2',
|
||||
injectCSS: '',
|
||||
injectHead: '',
|
||||
injectBody: '',
|
||||
sidebarPosition: 'left',
|
||||
tocPosition: 'right',
|
||||
showSharingMenu: true,
|
||||
showPrintBtn: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// -> DEFAULT GROUPS
|
||||
|
||||
const groupAdminId = uuid()
|
||||
const groupGuestId = '10000000-0000-4000-0000-000000000001'
|
||||
await knex('groups').insert([
|
||||
{
|
||||
id: groupAdminId,
|
||||
name: 'Administrators',
|
||||
permissions: JSON.stringify(['manage:system']),
|
||||
rules: JSON.stringify([]),
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
id: groupGuestId,
|
||||
name: 'Guests',
|
||||
permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
|
||||
rules: JSON.stringify([
|
||||
{
|
||||
id: uuid(),
|
||||
name: 'Default Rule',
|
||||
roles: ['read:pages', 'read:assets', 'read:comments'],
|
||||
match: 'START',
|
||||
mode: 'DENY',
|
||||
path: '',
|
||||
locales: [],
|
||||
sites: []
|
||||
}
|
||||
]),
|
||||
isSystem: true
|
||||
}
|
||||
])
|
||||
|
||||
// -> AUTHENTICATION MODULE
|
||||
|
||||
const authModuleId = uuid()
|
||||
await knex('authentication').insert({
|
||||
id: authModuleId,
|
||||
module: 'local',
|
||||
isEnabled: true,
|
||||
displayName: 'Local Authentication'
|
||||
})
|
||||
|
||||
// -> USERS
|
||||
|
||||
const userAdminId = uuid()
|
||||
const userGuestId = uuid()
|
||||
await knex('users').insert([
|
||||
{
|
||||
id: userAdminId,
|
||||
email: process.env.ADMIN_EMAIL ?? 'admin@example.com',
|
||||
auth: {
|
||||
[authModuleId]: {
|
||||
password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
|
||||
mustChangePwd: !process.env.ADMIN_PASS,
|
||||
restrictLogin: false,
|
||||
tfaRequired: false,
|
||||
tfaSecret: ''
|
||||
}
|
||||
},
|
||||
name: 'Administrator',
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
isVerified: true,
|
||||
meta: {
|
||||
location: '',
|
||||
jobTitle: '',
|
||||
pronouns: ''
|
||||
},
|
||||
prefs: {
|
||||
timezone: 'America/New_York',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: '12h',
|
||||
darkMode: false
|
||||
},
|
||||
localeCode: 'en'
|
||||
},
|
||||
{
|
||||
id: userGuestId,
|
||||
email: 'guest@example.com',
|
||||
name: 'Guest',
|
||||
isSystem: true,
|
||||
isActive: true,
|
||||
isVerified: true,
|
||||
localeCode: 'en'
|
||||
}
|
||||
])
|
||||
|
||||
await knex('userGroups').insert([
|
||||
{
|
||||
userId: userAdminId,
|
||||
groupId: groupAdminId
|
||||
},
|
||||
{
|
||||
userId: userGuestId,
|
||||
groupId: groupGuestId
|
||||
}
|
||||
])
|
||||
|
||||
// -> STORAGE MODULE
|
||||
|
||||
await knex('storage').insert({
|
||||
module: 'db',
|
||||
siteId,
|
||||
isEnabled: true,
|
||||
contentTypes: {
|
||||
activeTypes: ['pages', 'images', 'documents', 'others', 'large'],
|
||||
largeThreshold: '5MB'
|
||||
},
|
||||
assetDelivery: {
|
||||
streaming: true,
|
||||
directAccess: false
|
||||
},
|
||||
versioning: {
|
||||
enabled: false
|
||||
},
|
||||
state: {
|
||||
current: 'ok'
|
||||
}
|
||||
})
|
||||
|
||||
WIKI.logger.info('Completed 3.0.0 database migration.')
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -0,0 +1,59 @@
|
||||
const { Kind, GraphQLScalarType } = require('graphql')
|
||||
|
||||
function ensureObject (value) {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
throw new TypeError(`JSONObject cannot represent non-object value: ${value}`)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function parseLiteral (typeName, ast, variables) {
|
||||
switch (ast.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
return ast.value
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
return parseFloat(ast.value)
|
||||
case Kind.OBJECT:
|
||||
return parseObject(typeName, ast, variables)
|
||||
case Kind.LIST:
|
||||
return ast.values.map((n) => parseLiteral(typeName, n, variables))
|
||||
case Kind.NULL:
|
||||
return null
|
||||
case Kind.VARIABLE:
|
||||
return variables ? variables[ast.name.value] : undefined
|
||||
default:
|
||||
throw new TypeError(`${typeName} cannot represent value: ${ast}`)
|
||||
}
|
||||
}
|
||||
|
||||
function parseObject (typeName, ast, variables) {
|
||||
const value = Object.create(null)
|
||||
ast.fields.forEach((field) => {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
value[field.name.value] = parseLiteral(typeName, field.value, variables)
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
JSON: new GraphQLScalarType({
|
||||
name: 'JSON',
|
||||
description:
|
||||
'The `JSON` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
|
||||
specifiedByUrl:
|
||||
'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
|
||||
serialize: ensureObject,
|
||||
parseValue: ensureObject,
|
||||
parseLiteral: (ast, variables) => {
|
||||
if (ast.kind !== Kind.OBJECT) {
|
||||
throw new TypeError(`JSONObject cannot represent non-object value: ${ast}`)
|
||||
}
|
||||
|
||||
return parseObject('JSONObject', ast, variables)
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
const { Kind, GraphQLScalarType } = require('graphql')
|
||||
// const { Kind } = require('graphql/language')
|
||||
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
||||
const nilUUID = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
function isUUID (value) {
|
||||
return uuidRegex.test(value) || nilUUID === value
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
UUID: new GraphQLScalarType({
|
||||
name: 'UUID',
|
||||
description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122).',
|
||||
serialize: (value) => {
|
||||
if (!isUUID(value)) {
|
||||
throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
|
||||
}
|
||||
|
||||
return value.toLowerCase()
|
||||
},
|
||||
parseValue: (value) => {
|
||||
if (!isUUID(value)) {
|
||||
throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
|
||||
}
|
||||
|
||||
return value.toLowerCase()
|
||||
},
|
||||
parseLiteral: (ast) => {
|
||||
if (ast.kind === Kind.STRING) {
|
||||
if (isUUID(ast.value)) {
|
||||
return ast.value
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
# SCALARS
|
||||
|
||||
scalar Date
|
||||
scalar JSON
|
||||
# scalar Upload
|
||||
scalar UUID
|
||||
|
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
@ -0,0 +1,6 @@
|
||||
/dist
|
||||
/src-capacitor
|
||||
/src-cordova
|
||||
/.quasar
|
||||
/node_modules
|
||||
.eslintrc.js
|
@ -0,0 +1,75 @@
|
||||
module.exports = {
|
||||
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
|
||||
// This option interrupts the configuration hierarchy at this file
|
||||
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
|
||||
root: true,
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: '2021' // Allows for the parsing of modern ECMAScript features
|
||||
},
|
||||
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
'vue/setup-compiler-macros': true
|
||||
},
|
||||
|
||||
// Rules order is important, please avoid shuffling them
|
||||
extends: [
|
||||
// Base ESLint recommended rules
|
||||
// 'eslint:recommended',
|
||||
|
||||
// Uncomment any of the lines below to choose desired strictness,
|
||||
// but leave only one uncommented!
|
||||
// See https://eslint.vuejs.org/rules/#available-rules
|
||||
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
|
||||
'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
|
||||
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
||||
|
||||
'standard'
|
||||
],
|
||||
|
||||
plugins: [
|
||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
||||
// required to lint *.vue files
|
||||
'vue'
|
||||
],
|
||||
|
||||
globals: {
|
||||
ga: 'readonly', // Google Analytics
|
||||
__statics: 'readonly',
|
||||
__QUASAR_SSR__: 'readonly',
|
||||
__QUASAR_SSR_SERVER__: 'readonly',
|
||||
__QUASAR_SSR_CLIENT__: 'readonly',
|
||||
__QUASAR_SSR_PWA__: 'readonly',
|
||||
process: 'readonly',
|
||||
APOLLO_CLIENT: 'readonly'
|
||||
},
|
||||
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 'off',
|
||||
'one-var': 'off',
|
||||
'no-void': 'off',
|
||||
'multiline-ternary': 'off',
|
||||
|
||||
'import/first': 'off',
|
||||
'import/named': 'error',
|
||||
'import/namespace': 'error',
|
||||
'import/default': 'error',
|
||||
'import/export': 'error',
|
||||
'import/extensions': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
|
||||
'no-unused-vars': 'off',
|
||||
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
.DS_Store
|
||||
.thumbs.db
|
||||
node_modules
|
||||
|
||||
# Quasar core related directories
|
||||
.quasar
|
||||
/dist
|
||||
|
||||
# Cordova related directories and files
|
||||
/src-cordova/node_modules
|
||||
/src-cordova/platforms
|
||||
/src-cordova/plugins
|
||||
/src-cordova/www
|
||||
|
||||
# Capacitor related directories and files
|
||||
/src-capacitor/www
|
||||
/src-capacitor/node_modules
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"johnsoncodehk.volar",
|
||||
"wayou.vscode-todo-highlight"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"octref.vetur",
|
||||
"hookyqr.beautify",
|
||||
"dbaeumer.jshint",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
]
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.codeActionsOnSave": [
|
||||
"source.fixAll.eslint"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"vue"
|
||||
],
|
||||
"i18n-ally.localesPaths": "src/i18n/locales"
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
# Wiki.js (ux)
|
||||
|
||||
The most powerful and extensible open source Wiki software
|
||||
|
||||
## Install the dependencies
|
||||
```bash
|
||||
yarn
|
||||
# or
|
||||
npm install
|
||||
```
|
||||
|
||||
### Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||
```bash
|
||||
quasar dev
|
||||
```
|
||||
|
||||
|
||||
### Lint the files
|
||||
```bash
|
||||
yarn lint
|
||||
# or
|
||||
npm run lint
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Build the app for production
|
||||
```bash
|
||||
quasar build
|
||||
```
|
||||
|
||||
### Customize the configuration
|
||||
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
client: {
|
||||
service: {
|
||||
name: 'wiki-core',
|
||||
// URL to the GraphQL API
|
||||
url: 'http://localhost:11511'
|
||||
},
|
||||
// Files processed by the extension
|
||||
includes: [
|
||||
'src/**/*.vue',
|
||||
'src/**/*.js'
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
|
||||
<title>Wiki.js</title>
|
||||
<!--preload-links-->
|
||||
<style type="text/css">
|
||||
@keyframes initspinner {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.init-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255,255,255,.75);
|
||||
z-index: 2000000000;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.body--dark .init-loading {
|
||||
background-color: rgba(0,0,0,.75);
|
||||
}
|
||||
|
||||
.init-loading:before {
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-top: -25px;
|
||||
margin-left: -25px;
|
||||
border-radius: 50%;
|
||||
border-top: 2px solid #1976D2;
|
||||
border-right: 2px solid transparent;
|
||||
animation: initspinner .6s linear infinite;
|
||||
z-index: 2000000000;
|
||||
}
|
||||
</style>
|
||||
|
||||
<noscript>
|
||||
<style type="text/css">
|
||||
.init-loading {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.q-drawer-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.scroll.relative-position {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
.scroll.relative-position > .absolute {
|
||||
position: relative !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="init-loading"></div>
|
||||
<div id="app"><!-- quasar:entry-point --></div>
|
||||
<script type="module" src="/entry-client.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": [
|
||||
"src/*"
|
||||
],
|
||||
"app/*": [
|
||||
"*"
|
||||
],
|
||||
"components/*": [
|
||||
"src/components/*"
|
||||
],
|
||||
"layouts/*": [
|
||||
"src/layouts/*"
|
||||
],
|
||||
"pages/*": [
|
||||
"src/pages/*"
|
||||
],
|
||||
"assets/*": [
|
||||
"src/assets/*"
|
||||
],
|
||||
"boot/*": [
|
||||
"src/boot/*"
|
||||
],
|
||||
"stores/*": [
|
||||
"src/stores/*"
|
||||
],
|
||||
"vue$": [
|
||||
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
".quasar",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
{
|
||||
"name": "ux",
|
||||
"version": "0.0.1",
|
||||
"description": "The most powerful and extensible open source Wiki software",
|
||||
"productName": "Wiki.js",
|
||||
"author": "Nicolas Giard <nick@requarks.io>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "quasar dev",
|
||||
"build": "quasar build",
|
||||
"lint": "eslint --ext .js,.vue ./"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.5.10",
|
||||
"@codemirror/autocomplete": "0.19.15",
|
||||
"@codemirror/basic-setup": "0.19.1",
|
||||
"@codemirror/closebrackets": "0.19.1",
|
||||
"@codemirror/commands": "0.19.8",
|
||||
"@codemirror/comment": "0.19.1",
|
||||
"@codemirror/fold": "0.19.3",
|
||||
"@codemirror/gutter": "0.19.9",
|
||||
"@codemirror/highlight": "0.19.8",
|
||||
"@codemirror/history": "0.19.2",
|
||||
"@codemirror/lang-css": "0.19.3",
|
||||
"@codemirror/lang-html": "0.19.4",
|
||||
"@codemirror/lang-javascript": "0.19.7",
|
||||
"@codemirror/lang-json": "0.19.2",
|
||||
"@codemirror/lang-markdown": "0.19.6",
|
||||
"@codemirror/matchbrackets": "0.19.4",
|
||||
"@codemirror/search": "0.19.9",
|
||||
"@codemirror/state": "0.19.9",
|
||||
"@codemirror/tooltip": "0.19.16",
|
||||
"@codemirror/view": "0.19.47",
|
||||
"@lezer/common": "0.15.12",
|
||||
"@quasar/extras": "1.13.5",
|
||||
"@tiptap/core": "2.0.0-beta.174",
|
||||
"@tiptap/extension-code-block": "2.0.0-beta.37",
|
||||
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
|
||||
"@tiptap/extension-color": "2.0.0-beta.9",
|
||||
"@tiptap/extension-dropcursor": "2.0.0-beta.25",
|
||||
"@tiptap/extension-font-family": "2.0.0-beta.21",
|
||||
"@tiptap/extension-gapcursor": "2.0.0-beta.34",
|
||||
"@tiptap/extension-hard-break": "2.0.0-beta.30",
|
||||
"@tiptap/extension-highlight": "2.0.0-beta.33",
|
||||
"@tiptap/extension-history": "2.0.0-beta.21",
|
||||
"@tiptap/extension-image": "2.0.0-beta.27",
|
||||
"@tiptap/extension-mention": "2.0.0-beta.95",
|
||||
"@tiptap/extension-placeholder": "2.0.0-beta.48",
|
||||
"@tiptap/extension-table": "2.0.0-beta.48",
|
||||
"@tiptap/extension-table-cell": "2.0.0-beta.20",
|
||||
"@tiptap/extension-table-header": "2.0.0-beta.22",
|
||||
"@tiptap/extension-table-row": "2.0.0-beta.19",
|
||||
"@tiptap/extension-task-item": "2.0.0-beta.31",
|
||||
"@tiptap/extension-task-list": "2.0.0-beta.26",
|
||||
"@tiptap/extension-text-align": "2.0.0-beta.29",
|
||||
"@tiptap/extension-text-style": "2.0.0-beta.23",
|
||||
"@tiptap/extension-typography": "2.0.0-beta.20",
|
||||
"@tiptap/starter-kit": "2.0.0-beta.183",
|
||||
"@tiptap/vue-3": "2.0.0-beta.90",
|
||||
"@vue/apollo-option": "4.0.0-alpha.16",
|
||||
"apollo-upload-client": "17.0.0",
|
||||
"browser-fs-access": "0.26.1",
|
||||
"clipboard": "2.0.10",
|
||||
"filesize": "8.0.7",
|
||||
"filesize-parser": "1.5.0",
|
||||
"graphql": "16.3.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"js-cookie": "3.0.1",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "2.3.1",
|
||||
"pinia": "2.0.13",
|
||||
"pug": "3.0.2",
|
||||
"quasar": "2.6.5",
|
||||
"tippy.js": "6.3.7",
|
||||
"uuid": "8.3.2",
|
||||
"v-network-graph": "0.5.9",
|
||||
"vue": "3.2.31",
|
||||
"vue-i18n": "9.1.9",
|
||||
"vue-router": "4.0.14",
|
||||
"vuedraggable": "4.1.0",
|
||||
"zxcvbn": "4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/vite-plugin-vue-i18n": "3.4.0",
|
||||
"@quasar/app-vite": "1.0.0-beta.13",
|
||||
"@types/lodash": "4.14.181",
|
||||
"autoprefixer": "10.4.4",
|
||||
"eslint": "8.12.0",
|
||||
"eslint-config-standard": "17.0.0-1",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-n": "15.1.0",
|
||||
"eslint-plugin-promise": "6.0.0",
|
||||
"eslint-plugin-vue": "8.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16",
|
||||
"npm": ">= 6.13.4",
|
||||
"yarn": ">= 1.21.1"
|
||||
},
|
||||
"eslint.packageManager": "yarn"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/* eslint-disable */
|
||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// https://github.com/postcss/autoprefixer
|
||||
require('autoprefixer')({
|
||||
overrideBrowserslist: [
|
||||
'last 4 Chrome versions',
|
||||
'last 4 Firefox versions',
|
||||
'last 4 Edge versions',
|
||||
'last 4 Safari versions',
|
||||
'last 4 Android versions',
|
||||
'last 4 ChromeAndroid versions',
|
||||
'last 4 FirefoxAndroid versions',
|
||||
'last 4 iOS versions'
|
||||
]
|
||||
})
|
||||
|
||||
// https://github.com/elchininet/postcss-rtlcss
|
||||
// If you want to support RTL css, then
|
||||
// 1. yarn/npm install postcss-rtlcss
|
||||
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
||||
// 3. uncomment the following line:
|
||||
// require('postcss-rtlcss')
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 578 KiB |
After Width: | Height: | Size: 588 KiB |
After Width: | Height: | Size: 627 KiB |
After Width: | Height: | Size: 263 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 453 B |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.3 KiB |