3.0.0.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  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. name: '',
  383. secure: true,
  384. verifySSL: true,
  385. user: '',
  386. pass: '',
  387. useDKIM: false,
  388. dkimDomainName: '',
  389. dkimKeySelector: '',
  390. dkimPrivateKey: ''
  391. }
  392. },
  393. {
  394. key: 'security',
  395. value: {
  396. corsConfig: '',
  397. corsMode: 'OFF',
  398. cspDirectives: '',
  399. disallowFloc: true,
  400. disallowIframe: true,
  401. disallowOpenRedirect: true,
  402. enforceCsp: false,
  403. enforceHsts: false,
  404. enforceSameOriginReferrerPolicy: true,
  405. forceAssetDownload: true,
  406. hstsDuration: 0,
  407. trustProxy: false,
  408. authJwtAudience: 'urn:wiki.js',
  409. authJwtExpiration: '30m',
  410. authJwtRenewablePeriod: '14d',
  411. uploadMaxFileSize: 10485760,
  412. uploadMaxFiles: 20,
  413. uploadScanSVG: true
  414. }
  415. },
  416. {
  417. key: 'update',
  418. value: {
  419. locales: true
  420. }
  421. }
  422. ])
  423. // -> DEFAULT LOCALE
  424. await knex('locales').insert({
  425. code: 'en',
  426. strings: {},
  427. isRTL: false,
  428. name: 'English',
  429. nativeName: 'English'
  430. })
  431. // -> DEFAULT SITE
  432. await knex('sites').insert({
  433. id: siteId,
  434. hostname: '*',
  435. isEnabled: true,
  436. config: {
  437. title: 'My Wiki Site',
  438. description: '',
  439. company: '',
  440. contentLicense: '',
  441. defaults: {
  442. timezone: 'America/New_York',
  443. dateFormat: 'YYYY-MM-DD',
  444. timeFormat: '12h'
  445. },
  446. features: {
  447. ratings: false,
  448. ratingsMode: 'off',
  449. comments: false,
  450. contributions: false,
  451. profile: true,
  452. search: true
  453. },
  454. logoText: true,
  455. sitemap: true,
  456. robots: {
  457. index: true,
  458. follow: true
  459. },
  460. locale: 'en',
  461. localeNamespacing: false,
  462. localeNamespaces: [],
  463. theme: {
  464. dark: false,
  465. colorPrimary: '#1976d2',
  466. colorSecondary: '#02c39a',
  467. colorAccent: '#f03a47',
  468. colorHeader: '#000000',
  469. colorSidebar: '#1976d2',
  470. injectCSS: '',
  471. injectHead: '',
  472. injectBody: '',
  473. sidebarPosition: 'left',
  474. tocPosition: 'right',
  475. showSharingMenu: true,
  476. showPrintBtn: true
  477. }
  478. }
  479. })
  480. // -> DEFAULT GROUPS
  481. await knex('groups').insert([
  482. {
  483. id: groupAdminId,
  484. name: 'Administrators',
  485. permissions: JSON.stringify(['manage:system']),
  486. rules: JSON.stringify([]),
  487. isSystem: true
  488. },
  489. {
  490. id: groupGuestId,
  491. name: 'Guests',
  492. permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
  493. rules: JSON.stringify([
  494. {
  495. id: uuid(),
  496. name: 'Default Rule',
  497. roles: ['read:pages', 'read:assets', 'read:comments'],
  498. match: 'START',
  499. mode: 'DENY',
  500. path: '',
  501. locales: [],
  502. sites: []
  503. }
  504. ]),
  505. isSystem: true
  506. }
  507. ])
  508. // -> AUTHENTICATION MODULE
  509. await knex('authentication').insert({
  510. id: authModuleId,
  511. module: 'local',
  512. isEnabled: true,
  513. displayName: 'Local Authentication'
  514. })
  515. // -> USERS
  516. await knex('users').insert([
  517. {
  518. id: userAdminId,
  519. email: process.env.ADMIN_EMAIL ?? 'admin@example.com',
  520. auth: {
  521. [authModuleId]: {
  522. password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
  523. mustChangePwd: !process.env.ADMIN_PASS,
  524. restrictLogin: false,
  525. tfaRequired: false,
  526. tfaSecret: ''
  527. }
  528. },
  529. name: 'Administrator',
  530. isSystem: false,
  531. isActive: true,
  532. isVerified: true,
  533. meta: {
  534. location: '',
  535. jobTitle: '',
  536. pronouns: ''
  537. },
  538. prefs: {
  539. timezone: 'America/New_York',
  540. dateFormat: 'YYYY-MM-DD',
  541. timeFormat: '12h',
  542. darkMode: false
  543. },
  544. localeCode: 'en'
  545. },
  546. {
  547. id: userGuestId,
  548. email: 'guest@example.com',
  549. name: 'Guest',
  550. isSystem: true,
  551. isActive: true,
  552. isVerified: true,
  553. localeCode: 'en'
  554. }
  555. ])
  556. await knex('userGroups').insert([
  557. {
  558. userId: userAdminId,
  559. groupId: groupAdminId
  560. },
  561. {
  562. userId: userGuestId,
  563. groupId: groupGuestId
  564. }
  565. ])
  566. // -> STORAGE MODULE
  567. await knex('storage').insert({
  568. module: 'db',
  569. siteId,
  570. isEnabled: true,
  571. contentTypes: {
  572. activeTypes: ['pages', 'images', 'documents', 'others', 'large'],
  573. largeThreshold: '5MB'
  574. },
  575. assetDelivery: {
  576. streaming: true,
  577. directAccess: false
  578. },
  579. versioning: {
  580. enabled: false
  581. },
  582. state: {
  583. current: 'ok'
  584. }
  585. })
  586. WIKI.logger.info('Completed 3.0.0 database migration.')
  587. }
  588. exports.down = knex => { }