@ -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
|
# SCALARS
|
||||||
|
|
||||||
scalar Date
|
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 |