3.0.0.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. const { v4: uuid } = require('uuid')
  2. const bcrypt = require('bcryptjs-then')
  3. const crypto = require('crypto')
  4. const pem2jwk = require('pem-jwk').pem2jwk
  5. /* global WIKI */
  6. exports.up = async knex => {
  7. WIKI.logger.info('Running 3.0.0 database migration...')
  8. // =====================================
  9. // PG EXTENSIONS
  10. // =====================================
  11. await knex.raw('CREATE EXTENSION IF NOT EXISTS pgcrypto;')
  12. await knex.schema
  13. // =====================================
  14. // MODEL TABLES
  15. // =====================================
  16. // ANALYTICS ---------------------------
  17. .createTable('analytics', table => {
  18. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  19. table.string('module').notNullable()
  20. table.boolean('isEnabled').notNullable().defaultTo(false)
  21. table.jsonb('config').notNullable()
  22. })
  23. // API KEYS ----------------------------
  24. .createTable('apiKeys', table => {
  25. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  26. table.string('name').notNullable()
  27. table.text('key').notNullable()
  28. table.string('expiration').notNullable()
  29. table.boolean('isRevoked').notNullable().defaultTo(false)
  30. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  31. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  32. })
  33. // ASSETS ------------------------------
  34. .createTable('assets', table => {
  35. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  36. table.string('filename').notNullable()
  37. table.string('hash').notNullable().index()
  38. table.string('ext').notNullable()
  39. table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
  40. table.string('mime').notNullable().defaultTo('application/octet-stream')
  41. table.integer('fileSize').unsigned().comment('In kilobytes')
  42. table.jsonb('metadata')
  43. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  44. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  45. })
  46. // ASSET DATA --------------------------
  47. .createTable('assetData', table => {
  48. table.uuid('id').notNullable().index()
  49. table.binary('data').notNullable()
  50. })
  51. // ASSET FOLDERS -----------------------
  52. .createTable('assetFolders', table => {
  53. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  54. table.string('name').notNullable()
  55. table.string('slug').notNullable()
  56. })
  57. // AUTHENTICATION ----------------------
  58. .createTable('authentication', table => {
  59. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  60. table.string('module').notNullable()
  61. table.boolean('isEnabled').notNullable().defaultTo(false)
  62. table.integer('order').unsigned().notNullable().defaultTo(0)
  63. table.string('displayName').notNullable().defaultTo('')
  64. table.jsonb('config').notNullable().defaultTo('{}')
  65. table.boolean('selfRegistration').notNullable().defaultTo(false)
  66. table.jsonb('domainWhitelist').notNullable().defaultTo('[]')
  67. table.jsonb('autoEnrollGroups').notNullable().defaultTo('[]')
  68. table.jsonb('hideOnSites').notNullable().defaultTo('[]')
  69. })
  70. .createTable('commentProviders', table => {
  71. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  72. table.string('module').notNullable()
  73. table.boolean('isEnabled').notNullable().defaultTo(false)
  74. table.json('config').notNullable()
  75. })
  76. // COMMENTS ----------------------------
  77. .createTable('comments', table => {
  78. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  79. table.uuid('replyTo')
  80. table.text('content').notNullable()
  81. table.text('render').notNullable().defaultTo('')
  82. table.string('name').notNullable().defaultTo('')
  83. table.string('email').notNullable().defaultTo('')
  84. table.string('ip').notNullable().defaultTo('')
  85. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  86. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  87. })
  88. // GROUPS ------------------------------
  89. .createTable('groups', table => {
  90. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  91. table.string('name').notNullable()
  92. table.jsonb('permissions').notNullable()
  93. table.jsonb('rules').notNullable()
  94. table.string('redirectOnLogin').notNullable().defaultTo('')
  95. table.string('redirectOnFirstLogin').notNullable().defaultTo('')
  96. table.string('redirectOnLogout').notNullable().defaultTo('')
  97. table.boolean('isSystem').notNullable().defaultTo(false)
  98. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  99. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  100. })
  101. // HOOKS -------------------------------
  102. .createTable('hooks', table => {
  103. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  104. table.string('name').notNullable()
  105. table.jsonb('events').notNullable().defaultTo('[]')
  106. table.string('url').notNullable()
  107. table.boolean('includeMetadata').notNullable().defaultTo(false)
  108. table.boolean('includeContent').notNullable().defaultTo(false)
  109. table.boolean('acceptUntrusted').notNullable().defaultTo(false)
  110. table.string('authHeader')
  111. table.enum('state', ['pending', 'error', 'success']).notNullable().defaultTo('pending')
  112. table.string('lastErrorMessage')
  113. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  114. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  115. })
  116. // LOCALES -----------------------------
  117. .createTable('locales', table => {
  118. table.string('code', 5).notNullable().primary()
  119. table.jsonb('strings')
  120. table.boolean('isRTL').notNullable().defaultTo(false)
  121. table.string('name').notNullable()
  122. table.string('nativeName').notNullable()
  123. table.integer('availability').notNullable().defaultTo(0)
  124. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  125. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  126. })
  127. // NAVIGATION ----------------------------
  128. .createTable('navigation', table => {
  129. table.string('key').notNullable().primary()
  130. table.jsonb('config')
  131. })
  132. // PAGE HISTORY ------------------------
  133. .createTable('pageHistory', table => {
  134. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  135. table.uuid('pageId').notNullable().index()
  136. table.string('path').notNullable()
  137. table.string('hash').notNullable()
  138. table.string('title').notNullable()
  139. table.string('description')
  140. table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
  141. table.timestamp('publishStartDate')
  142. table.timestamp('publishEndDate')
  143. table.string('action').defaultTo('updated')
  144. table.text('content')
  145. table.string('editor').notNullable()
  146. table.string('contentType').notNullable()
  147. table.jsonb('extra').notNullable().defaultTo('{}')
  148. table.jsonb('tags').defaultTo('[]')
  149. table.timestamp('versionDate').notNullable().defaultTo(knex.fn.now())
  150. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  151. })
  152. // PAGE LINKS --------------------------
  153. .createTable('pageLinks', table => {
  154. table.increments('id').primary()
  155. table.string('path').notNullable()
  156. table.string('localeCode', 5).notNullable()
  157. })
  158. // PAGES -------------------------------
  159. .createTable('pages', table => {
  160. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  161. table.string('slug')
  162. table.string('path').notNullable()
  163. table.string('hash').notNullable()
  164. table.string('title').notNullable()
  165. table.string('description')
  166. table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
  167. table.timestamp('publishStartDate')
  168. table.timestamp('publishEndDate')
  169. table.text('content')
  170. table.text('render')
  171. table.jsonb('toc')
  172. table.string('editor').notNullable()
  173. table.string('contentType').notNullable()
  174. table.jsonb('extra').notNullable().defaultTo('{}')
  175. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  176. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  177. })
  178. // PAGE TREE ---------------------------
  179. .createTable('pageTree', table => {
  180. table.integer('id').unsigned().primary()
  181. table.string('path').notNullable()
  182. table.integer('depth').unsigned().notNullable()
  183. table.string('title').notNullable()
  184. table.boolean('isFolder').notNullable().defaultTo(false)
  185. table.jsonb('ancestors')
  186. })
  187. // RENDERERS ---------------------------
  188. .createTable('renderers', table => {
  189. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  190. table.string('module').notNullable()
  191. table.boolean('isEnabled').notNullable().defaultTo(false)
  192. table.jsonb('config')
  193. })
  194. // SETTINGS ----------------------------
  195. .createTable('settings', table => {
  196. table.string('key').notNullable().primary()
  197. table.jsonb('value')
  198. })
  199. // SITES -------------------------------
  200. .createTable('sites', table => {
  201. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  202. table.string('hostname').notNullable()
  203. table.boolean('isEnabled').notNullable().defaultTo(false)
  204. table.jsonb('config').notNullable()
  205. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  206. })
  207. // STORAGE -----------------------------
  208. .createTable('storage', table => {
  209. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  210. table.string('module').notNullable()
  211. table.boolean('isEnabled').notNullable().defaultTo(false)
  212. table.jsonb('contentTypes')
  213. table.jsonb('assetDelivery')
  214. table.jsonb('versioning')
  215. table.jsonb('schedule')
  216. table.jsonb('config')
  217. table.jsonb('state')
  218. })
  219. // TAGS --------------------------------
  220. .createTable('tags', table => {
  221. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  222. table.string('tag').notNullable()
  223. table.jsonb('display').notNullable().defaultTo('{}')
  224. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  225. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  226. })
  227. // USER AVATARS ------------------------
  228. .createTable('userAvatars', table => {
  229. table.uuid('id').notNullable().primary()
  230. table.binary('data').notNullable()
  231. })
  232. // USER KEYS ---------------------------
  233. .createTable('userKeys', table => {
  234. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  235. table.string('kind').notNullable()
  236. table.string('token').notNullable()
  237. table.timestamp('validUntil').notNullable()
  238. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  239. })
  240. // USERS -------------------------------
  241. .createTable('users', table => {
  242. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  243. table.string('email').notNullable()
  244. table.string('name').notNullable()
  245. table.jsonb('auth')
  246. table.jsonb('tfa')
  247. table.jsonb('meta')
  248. table.jsonb('prefs')
  249. table.string('pictureUrl')
  250. table.boolean('isSystem').notNullable().defaultTo(false)
  251. table.boolean('isActive').notNullable().defaultTo(false)
  252. table.boolean('isVerified').notNullable().defaultTo(false)
  253. table.timestamp('lastLoginAt').index()
  254. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  255. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  256. })
  257. // =====================================
  258. // RELATION TABLES
  259. // =====================================
  260. // PAGE TAGS ---------------------------
  261. .createTable('pageTags', table => {
  262. table.increments('id').primary()
  263. table.uuid('pageId').references('id').inTable('pages').onDelete('CASCADE')
  264. table.uuid('tagId').references('id').inTable('tags').onDelete('CASCADE')
  265. })
  266. // USER GROUPS -------------------------
  267. .createTable('userGroups', table => {
  268. table.increments('id').primary()
  269. table.uuid('userId').references('id').inTable('users').onDelete('CASCADE')
  270. table.uuid('groupId').references('id').inTable('groups').onDelete('CASCADE')
  271. })
  272. // =====================================
  273. // REFERENCES
  274. // =====================================
  275. .table('analytics', table => {
  276. table.uuid('siteId').notNullable().references('id').inTable('sites')
  277. })
  278. .table('assets', table => {
  279. table.uuid('folderId').notNullable().references('id').inTable('assetFolders').index()
  280. table.uuid('authorId').notNullable().references('id').inTable('users')
  281. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  282. })
  283. .table('assetFolders', table => {
  284. table.uuid('parentId').references('id').inTable('assetFolders').index()
  285. })
  286. .table('commentProviders', table => {
  287. table.uuid('siteId').notNullable().references('id').inTable('sites')
  288. })
  289. .table('comments', table => {
  290. table.uuid('pageId').notNullable().references('id').inTable('pages').index()
  291. table.uuid('authorId').notNullable().references('id').inTable('users').index()
  292. })
  293. .table('navigation', table => {
  294. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  295. })
  296. .table('pageHistory', table => {
  297. table.string('localeCode', 5).references('code').inTable('locales')
  298. table.uuid('authorId').notNullable().references('id').inTable('users')
  299. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  300. })
  301. .table('pageLinks', table => {
  302. table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
  303. table.index(['path', 'localeCode'])
  304. })
  305. .table('pages', table => {
  306. table.string('localeCode', 5).references('code').inTable('locales').index()
  307. table.uuid('authorId').notNullable().references('id').inTable('users').index()
  308. table.uuid('creatorId').notNullable().references('id').inTable('users').index()
  309. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  310. })
  311. .table('pageTree', table => {
  312. table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
  313. table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
  314. table.string('localeCode', 5).references('code').inTable('locales')
  315. })
  316. .table('renderers', table => {
  317. table.uuid('siteId').notNullable().references('id').inTable('sites')
  318. })
  319. .table('storage', table => {
  320. table.uuid('siteId').notNullable().references('id').inTable('sites')
  321. })
  322. .table('tags', table => {
  323. table.uuid('siteId').notNullable().references('id').inTable('sites')
  324. table.unique(['siteId', 'tag'])
  325. })
  326. .table('userKeys', table => {
  327. table.uuid('userId').notNullable().references('id').inTable('users')
  328. })
  329. .table('users', table => {
  330. table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
  331. })
  332. // =====================================
  333. // DEFAULT DATA
  334. // =====================================
  335. // -> GENERATE IDS
  336. const groupAdminId = uuid()
  337. const groupGuestId = '10000000-0000-4000-8000-000000000001'
  338. const siteId = uuid()
  339. const authModuleId = uuid()
  340. const userAdminId = uuid()
  341. const userGuestId = uuid()
  342. // -> SYSTEM CONFIG
  343. WIKI.logger.info('Generating certificates...')
  344. const secret = crypto.randomBytes(32).toString('hex')
  345. const certs = crypto.generateKeyPairSync('rsa', {
  346. modulusLength: 2048,
  347. publicKeyEncoding: {
  348. type: 'pkcs1',
  349. format: 'pem'
  350. },
  351. privateKeyEncoding: {
  352. type: 'pkcs1',
  353. format: 'pem',
  354. cipher: 'aes-256-cbc',
  355. passphrase: secret
  356. }
  357. })
  358. await knex('settings').insert([
  359. {
  360. key: 'auth',
  361. value: {
  362. audience: 'urn:wiki.js',
  363. tokenExpiration: '30m',
  364. tokenRenewal: '14d',
  365. certs: {
  366. jwk: pem2jwk(certs.publicKey),
  367. public: certs.publicKey,
  368. private: certs.privateKey
  369. },
  370. secret,
  371. rootAdminUserId: userAdminId,
  372. guestUserId: userGuestId
  373. }
  374. },
  375. {
  376. key: 'mail',
  377. value: {
  378. senderName: '',
  379. senderEmail: '',
  380. host: '',
  381. port: 465,
  382. secure: true,
  383. verifySSL: true,
  384. user: '',
  385. pass: '',
  386. useDKIM: false,
  387. dkimDomainName: '',
  388. dkimKeySelector: '',
  389. dkimPrivateKey: ''
  390. }
  391. },
  392. {
  393. key: 'security',
  394. value: {
  395. corsConfig: '',
  396. corsMode: 'OFF',
  397. cspDirectives: '',
  398. disallowFloc: true,
  399. disallowIframe: true,
  400. disallowOpenRedirect: true,
  401. enforceCsp: false,
  402. enforceHsts: false,
  403. enforceSameOriginReferrerPolicy: true,
  404. forceAssetDownload: true,
  405. hstsDuration: 0,
  406. trustProxy: false,
  407. authJwtAudience: 'urn:wiki.js',
  408. authJwtExpiration: '30m',
  409. authJwtRenewablePeriod: '14d',
  410. uploadMaxFileSize: 10485760,
  411. uploadMaxFiles: 20,
  412. uploadScanSVG: true
  413. }
  414. },
  415. {
  416. key: 'update',
  417. value: {
  418. locales: true
  419. }
  420. }
  421. ])
  422. // -> DEFAULT LOCALE
  423. await knex('locales').insert({
  424. code: 'en',
  425. strings: {},
  426. isRTL: false,
  427. name: 'English',
  428. nativeName: 'English'
  429. })
  430. // -> DEFAULT SITE
  431. await knex('sites').insert({
  432. id: siteId,
  433. hostname: '*',
  434. isEnabled: true,
  435. config: {
  436. title: 'My Wiki Site',
  437. description: '',
  438. company: '',
  439. contentLicense: '',
  440. defaults: {
  441. timezone: 'America/New_York',
  442. dateFormat: 'YYYY-MM-DD',
  443. timeFormat: '12h'
  444. },
  445. features: {
  446. ratings: false,
  447. ratingsMode: 'off',
  448. comments: false,
  449. contributions: false,
  450. profile: true,
  451. search: true
  452. },
  453. logoText: true,
  454. sitemap: true,
  455. robots: {
  456. index: true,
  457. follow: true
  458. },
  459. locale: 'en',
  460. localeNamespacing: false,
  461. localeNamespaces: [],
  462. theme: {
  463. dark: false,
  464. colorPrimary: '#1976d2',
  465. colorSecondary: '#02c39a',
  466. colorAccent: '#f03a47',
  467. colorHeader: '#000000',
  468. colorSidebar: '#1976d2',
  469. injectCSS: '',
  470. injectHead: '',
  471. injectBody: '',
  472. sidebarPosition: 'left',
  473. tocPosition: 'right',
  474. showSharingMenu: true,
  475. showPrintBtn: true
  476. }
  477. }
  478. })
  479. // -> DEFAULT GROUPS
  480. await knex('groups').insert([
  481. {
  482. id: groupAdminId,
  483. name: 'Administrators',
  484. permissions: JSON.stringify(['manage:system']),
  485. rules: JSON.stringify([]),
  486. isSystem: true
  487. },
  488. {
  489. id: groupGuestId,
  490. name: 'Guests',
  491. permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
  492. rules: JSON.stringify([
  493. {
  494. id: uuid(),
  495. name: 'Default Rule',
  496. roles: ['read:pages', 'read:assets', 'read:comments'],
  497. match: 'START',
  498. mode: 'DENY',
  499. path: '',
  500. locales: [],
  501. sites: []
  502. }
  503. ]),
  504. isSystem: true
  505. }
  506. ])
  507. // -> AUTHENTICATION MODULE
  508. await knex('authentication').insert({
  509. id: authModuleId,
  510. module: 'local',
  511. isEnabled: true,
  512. displayName: 'Local Authentication'
  513. })
  514. // -> USERS
  515. await knex('users').insert([
  516. {
  517. id: userAdminId,
  518. email: process.env.ADMIN_EMAIL ?? 'admin@example.com',
  519. auth: {
  520. [authModuleId]: {
  521. password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
  522. mustChangePwd: !process.env.ADMIN_PASS,
  523. restrictLogin: false,
  524. tfaRequired: false,
  525. tfaSecret: ''
  526. }
  527. },
  528. name: 'Administrator',
  529. isSystem: false,
  530. isActive: true,
  531. isVerified: true,
  532. meta: {
  533. location: '',
  534. jobTitle: '',
  535. pronouns: ''
  536. },
  537. prefs: {
  538. timezone: 'America/New_York',
  539. dateFormat: 'YYYY-MM-DD',
  540. timeFormat: '12h',
  541. darkMode: false
  542. },
  543. localeCode: 'en'
  544. },
  545. {
  546. id: userGuestId,
  547. email: 'guest@example.com',
  548. name: 'Guest',
  549. isSystem: true,
  550. isActive: true,
  551. isVerified: true,
  552. localeCode: 'en'
  553. }
  554. ])
  555. await knex('userGroups').insert([
  556. {
  557. userId: userAdminId,
  558. groupId: groupAdminId
  559. },
  560. {
  561. userId: userGuestId,
  562. groupId: groupGuestId
  563. }
  564. ])
  565. // -> STORAGE MODULE
  566. await knex('storage').insert({
  567. module: 'db',
  568. siteId,
  569. isEnabled: true,
  570. contentTypes: {
  571. activeTypes: ['pages', 'images', 'documents', 'others', 'large'],
  572. largeThreshold: '5MB'
  573. },
  574. assetDelivery: {
  575. streaming: true,
  576. directAccess: false
  577. },
  578. versioning: {
  579. enabled: false
  580. },
  581. state: {
  582. current: 'ok'
  583. }
  584. })
  585. WIKI.logger.info('Completed 3.0.0 database migration.')
  586. }
  587. exports.down = knex => { }