3.0.0.mjs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. import { v4 as uuid } from 'uuid'
  2. import bcrypt from 'bcryptjs'
  3. import crypto from 'node:crypto'
  4. import { DateTime } from 'luxon'
  5. import { pem2jwk } from 'pem-jwk'
  6. export async function up (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 ltree;')
  12. await knex.raw('CREATE EXTENSION IF NOT EXISTS pgcrypto;')
  13. await knex.schema
  14. // =====================================
  15. // MODEL TABLES
  16. // =====================================
  17. // ACTIVITY LOGS -----------------------
  18. .createTable('activityLogs', table => {
  19. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  20. table.timestamp('ts').notNullable().defaultTo(knex.fn.now())
  21. table.string('action').notNullable()
  22. table.jsonb('meta').notNullable()
  23. })
  24. // ANALYTICS ---------------------------
  25. .createTable('analytics', table => {
  26. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  27. table.string('module').notNullable()
  28. table.boolean('isEnabled').notNullable().defaultTo(false)
  29. table.jsonb('config').notNullable()
  30. })
  31. // API KEYS ----------------------------
  32. .createTable('apiKeys', table => {
  33. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  34. table.string('name').notNullable()
  35. table.text('key').notNullable()
  36. table.timestamp('expiration').notNullable().defaultTo(knex.fn.now())
  37. table.boolean('isRevoked').notNullable().defaultTo(false)
  38. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  39. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  40. })
  41. // ASSETS ------------------------------
  42. .createTable('assets', table => {
  43. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  44. table.string('fileName').notNullable()
  45. table.string('fileExt').notNullable()
  46. table.boolean('isSystem').notNullable().defaultTo(false)
  47. table.enum('kind', ['document', 'image', 'other']).notNullable().defaultTo('other')
  48. table.string('mimeType').notNullable().defaultTo('application/octet-stream')
  49. table.integer('fileSize').unsigned().comment('In bytes')
  50. table.jsonb('meta').notNullable().defaultTo('{}')
  51. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  52. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  53. table.binary('data')
  54. table.binary('preview')
  55. table.enum('previewState', ['none', 'pending', 'ready', 'failed']).notNullable().defaultTo('none')
  56. table.jsonb('storageInfo')
  57. })
  58. // AUTHENTICATION ----------------------
  59. .createTable('authentication', table => {
  60. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  61. table.string('module').notNullable()
  62. table.boolean('isEnabled').notNullable().defaultTo(false)
  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. })
  69. .createTable('commentProviders', table => {
  70. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  71. table.string('module').notNullable()
  72. table.boolean('isEnabled').notNullable().defaultTo(false)
  73. table.json('config').notNullable()
  74. })
  75. // COMMENTS ----------------------------
  76. .createTable('comments', table => {
  77. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  78. table.uuid('replyTo')
  79. table.text('content').notNullable()
  80. table.text('render').notNullable().defaultTo('')
  81. table.string('name').notNullable().defaultTo('')
  82. table.string('email').notNullable().defaultTo('')
  83. table.string('ip').notNullable().defaultTo('')
  84. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  85. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  86. })
  87. // GROUPS ------------------------------
  88. .createTable('groups', table => {
  89. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  90. table.string('name').notNullable()
  91. table.jsonb('permissions').notNullable()
  92. table.jsonb('rules').notNullable()
  93. table.string('redirectOnLogin').notNullable().defaultTo('')
  94. table.string('redirectOnFirstLogin').notNullable().defaultTo('')
  95. table.string('redirectOnLogout').notNullable().defaultTo('')
  96. table.boolean('isSystem').notNullable().defaultTo(false)
  97. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  98. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  99. })
  100. // HOOKS -------------------------------
  101. .createTable('hooks', table => {
  102. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  103. table.string('name').notNullable()
  104. table.jsonb('events').notNullable().defaultTo('[]')
  105. table.string('url').notNullable()
  106. table.boolean('includeMetadata').notNullable().defaultTo(false)
  107. table.boolean('includeContent').notNullable().defaultTo(false)
  108. table.boolean('acceptUntrusted').notNullable().defaultTo(false)
  109. table.string('authHeader')
  110. table.enum('state', ['pending', 'error', 'success']).notNullable().defaultTo('pending')
  111. table.string('lastErrorMessage')
  112. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  113. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  114. })
  115. // JOB HISTORY -------------------------
  116. .createTable('jobHistory', table => {
  117. table.uuid('id').notNullable().primary()
  118. table.string('task').notNullable()
  119. table.enum('state', ['active', 'completed', 'failed', 'interrupted']).notNullable()
  120. table.boolean('useWorker').notNullable().defaultTo(false)
  121. table.boolean('wasScheduled').notNullable().defaultTo(false)
  122. table.jsonb('payload')
  123. table.integer('attempt').notNullable().defaultTo(1)
  124. table.integer('maxRetries').notNullable().defaultTo(0)
  125. table.text('lastErrorMessage')
  126. table.string('executedBy')
  127. table.timestamp('createdAt').notNullable()
  128. table.timestamp('startedAt').notNullable().defaultTo(knex.fn.now())
  129. table.timestamp('completedAt')
  130. })
  131. // JOB SCHEDULE ------------------------
  132. .createTable('jobSchedule', table => {
  133. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  134. table.string('task').notNullable()
  135. table.string('cron').notNullable()
  136. table.string('type').notNullable().defaultTo('system')
  137. table.jsonb('payload')
  138. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  139. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  140. })
  141. // JOB SCHEDULE ------------------------
  142. .createTable('jobLock', table => {
  143. table.string('key').notNullable().primary()
  144. table.string('lastCheckedBy')
  145. table.timestamp('lastCheckedAt').notNullable().defaultTo(knex.fn.now())
  146. })
  147. // JOBS --------------------------------
  148. .createTable('jobs', table => {
  149. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  150. table.string('task').notNullable()
  151. table.boolean('useWorker').notNullable().defaultTo(false)
  152. table.jsonb('payload')
  153. table.integer('retries').notNullable().defaultTo(0)
  154. table.integer('maxRetries').notNullable().defaultTo(0)
  155. table.timestamp('waitUntil')
  156. table.boolean('isScheduled').notNullable().defaultTo(false)
  157. table.string('createdBy')
  158. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  159. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  160. })
  161. // LOCALES -----------------------------
  162. .createTable('locales', table => {
  163. table.string('code', 10).notNullable().primary()
  164. table.string('name').notNullable()
  165. table.string('nativeName').notNullable()
  166. table.string('language', 2).notNullable().index()
  167. table.string('region', 2)
  168. table.string('script', 4)
  169. table.boolean('isRTL').notNullable().defaultTo(false)
  170. table.jsonb('strings')
  171. table.integer('completeness').notNullable().defaultTo(0)
  172. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  173. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  174. })
  175. // NAVIGATION ----------------------------
  176. .createTable('navigation', table => {
  177. table.string('key').notNullable().primary()
  178. table.jsonb('config')
  179. })
  180. // PAGE HISTORY ------------------------
  181. .createTable('pageHistory', table => {
  182. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  183. table.uuid('pageId').notNullable().index()
  184. table.string('action').defaultTo('updated')
  185. table.jsonb('affectedFields').notNullable().defaultTo('[]')
  186. table.string('path').notNullable()
  187. table.string('hash').notNullable()
  188. table.string('alias')
  189. table.string('title').notNullable()
  190. table.string('description')
  191. table.string('icon')
  192. table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
  193. table.timestamp('publishStartDate')
  194. table.timestamp('publishEndDate')
  195. table.jsonb('config').notNullable().defaultTo('{}')
  196. table.jsonb('relations').notNullable().defaultTo('[]')
  197. table.text('content')
  198. table.text('render')
  199. table.jsonb('toc')
  200. table.string('editor').notNullable()
  201. table.string('contentType').notNullable()
  202. table.jsonb('scripts').notNullable().defaultTo('{}')
  203. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  204. table.timestamp('versionDate').notNullable().defaultTo(knex.fn.now())
  205. })
  206. // PAGE LINKS --------------------------
  207. .createTable('pageLinks', table => {
  208. table.increments('id').primary()
  209. table.string('path').notNullable()
  210. table.string('localeCode', 10).notNullable()
  211. })
  212. // PAGES -------------------------------
  213. .createTable('pages', table => {
  214. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  215. table.string('path').notNullable()
  216. table.string('hash').notNullable()
  217. table.string('alias')
  218. table.string('title').notNullable()
  219. table.string('description')
  220. table.string('icon')
  221. table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
  222. table.timestamp('publishStartDate')
  223. table.timestamp('publishEndDate')
  224. table.jsonb('config').notNullable().defaultTo('{}')
  225. table.jsonb('relations').notNullable().defaultTo('[]')
  226. table.text('content')
  227. table.text('render')
  228. table.jsonb('toc')
  229. table.string('editor').notNullable()
  230. table.string('contentType').notNullable()
  231. table.boolean('isBrowsable').notNullable().defaultTo(true)
  232. table.string('password')
  233. table.integer('ratingScore').notNullable().defaultTo(0)
  234. table.integer('ratingCount').notNullable().defaultTo(0)
  235. table.jsonb('scripts').notNullable().defaultTo('{}')
  236. table.jsonb('historyData').notNullable().defaultTo('{}')
  237. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  238. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  239. })
  240. // RENDERERS ---------------------------
  241. .createTable('renderers', table => {
  242. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  243. table.string('module').notNullable()
  244. table.boolean('isEnabled').notNullable().defaultTo(false)
  245. table.jsonb('config').notNullable().defaultTo('{}')
  246. })
  247. // SETTINGS ----------------------------
  248. .createTable('settings', table => {
  249. table.string('key').notNullable().primary()
  250. table.jsonb('value')
  251. })
  252. // SITES -------------------------------
  253. .createTable('sites', table => {
  254. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  255. table.string('hostname').notNullable()
  256. table.boolean('isEnabled').notNullable().defaultTo(false)
  257. table.jsonb('config').notNullable()
  258. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  259. })
  260. // STORAGE -----------------------------
  261. .createTable('storage', table => {
  262. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  263. table.string('module').notNullable()
  264. table.boolean('isEnabled').notNullable().defaultTo(false)
  265. table.jsonb('contentTypes')
  266. table.jsonb('assetDelivery')
  267. table.jsonb('versioning')
  268. table.jsonb('schedule')
  269. table.jsonb('config')
  270. table.jsonb('state')
  271. })
  272. // TAGS --------------------------------
  273. .createTable('tags', table => {
  274. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  275. table.string('tag').notNullable()
  276. table.jsonb('display').notNullable().defaultTo('{}')
  277. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  278. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  279. })
  280. // TREE --------------------------------
  281. .createTable('tree', table => {
  282. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  283. table.specificType('folderPath', 'ltree').index().index('tree_folderpath_gist_index', { indexType: 'GIST' })
  284. table.string('fileName').notNullable().index()
  285. table.string('hash').notNullable().index()
  286. table.enu('type', ['folder', 'page', 'asset']).notNullable().index()
  287. table.string('localeCode', 10).notNullable().defaultTo('en').index()
  288. table.string('title').notNullable()
  289. table.jsonb('meta').notNullable().defaultTo('{}')
  290. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  291. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  292. })
  293. // USER AVATARS ------------------------
  294. .createTable('userAvatars', table => {
  295. table.uuid('id').notNullable().primary()
  296. table.binary('data').notNullable()
  297. })
  298. // USER EDITOR SETTINGS ----------------
  299. .createTable('userEditorSettings', table => {
  300. table.uuid('id').notNullable()
  301. table.string('editor').notNullable()
  302. table.jsonb('config').notNullable().defaultTo('{}')
  303. table.primary(['id', 'editor'])
  304. })
  305. // USER KEYS ---------------------------
  306. .createTable('userKeys', table => {
  307. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  308. table.string('kind').notNullable()
  309. table.string('token').notNullable()
  310. table.jsonb('meta').notNullable().defaultTo('{}')
  311. table.timestamp('validUntil').notNullable()
  312. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  313. })
  314. // USERS -------------------------------
  315. .createTable('users', table => {
  316. table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
  317. table.string('email').notNullable()
  318. table.string('name').notNullable()
  319. table.jsonb('auth').notNullable().defaultTo('{}')
  320. table.jsonb('meta').notNullable().defaultTo('{}')
  321. table.jsonb('prefs').notNullable().defaultTo('{}')
  322. table.boolean('hasAvatar').notNullable().defaultTo(false)
  323. table.boolean('isSystem').notNullable().defaultTo(false)
  324. table.boolean('isActive').notNullable().defaultTo(false)
  325. table.boolean('isVerified').notNullable().defaultTo(false)
  326. table.timestamp('lastLoginAt').index()
  327. table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
  328. table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
  329. })
  330. // =====================================
  331. // RELATION TABLES
  332. // =====================================
  333. // PAGE TAGS ---------------------------
  334. .createTable('pageTags', table => {
  335. table.increments('id').primary()
  336. table.uuid('pageId').references('id').inTable('pages').onDelete('CASCADE')
  337. table.uuid('tagId').references('id').inTable('tags').onDelete('CASCADE')
  338. })
  339. // USER GROUPS -------------------------
  340. .createTable('userGroups', table => {
  341. table.increments('id').primary()
  342. table.uuid('userId').references('id').inTable('users').onDelete('CASCADE')
  343. table.uuid('groupId').references('id').inTable('groups').onDelete('CASCADE')
  344. })
  345. // =====================================
  346. // REFERENCES
  347. // =====================================
  348. .table('activityLogs', table => {
  349. table.uuid('userId').notNullable().references('id').inTable('users')
  350. })
  351. .table('analytics', table => {
  352. table.uuid('siteId').notNullable().references('id').inTable('sites')
  353. })
  354. .table('assets', table => {
  355. table.uuid('authorId').notNullable().references('id').inTable('users')
  356. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  357. })
  358. .table('commentProviders', table => {
  359. table.uuid('siteId').notNullable().references('id').inTable('sites')
  360. })
  361. .table('comments', table => {
  362. table.uuid('pageId').notNullable().references('id').inTable('pages').index()
  363. table.uuid('authorId').notNullable().references('id').inTable('users').index()
  364. })
  365. .table('navigation', table => {
  366. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  367. })
  368. .table('pageHistory', table => {
  369. table.string('localeCode', 10).references('code').inTable('locales')
  370. table.uuid('authorId').notNullable().references('id').inTable('users')
  371. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  372. })
  373. .table('pageLinks', table => {
  374. table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
  375. table.index(['path', 'localeCode'])
  376. })
  377. .table('pages', table => {
  378. table.string('localeCode', 10).references('code').inTable('locales').index()
  379. table.uuid('authorId').notNullable().references('id').inTable('users').index()
  380. table.uuid('creatorId').notNullable().references('id').inTable('users').index()
  381. table.uuid('ownerId').notNullable().references('id').inTable('users').index()
  382. table.uuid('siteId').notNullable().references('id').inTable('sites').index()
  383. })
  384. .table('storage', table => {
  385. table.uuid('siteId').notNullable().references('id').inTable('sites')
  386. })
  387. .table('tags', table => {
  388. table.uuid('siteId').notNullable().references('id').inTable('sites')
  389. table.unique(['siteId', 'tag'])
  390. })
  391. .table('tree', table => {
  392. table.uuid('siteId').notNullable().references('id').inTable('sites')
  393. })
  394. .table('userKeys', table => {
  395. table.uuid('userId').notNullable().references('id').inTable('users')
  396. })
  397. // =====================================
  398. // DEFAULT DATA
  399. // =====================================
  400. // -> GENERATE IDS
  401. const groupAdminId = uuid()
  402. const groupGuestId = '10000000-0000-4000-8000-000000000001'
  403. const siteId = uuid()
  404. const authModuleId = uuid()
  405. const userAdminId = uuid()
  406. const userGuestId = uuid()
  407. // -> SYSTEM CONFIG
  408. WIKI.logger.info('Generating certificates...')
  409. const secret = crypto.randomBytes(32).toString('hex')
  410. const certs = crypto.generateKeyPairSync('rsa', {
  411. modulusLength: 2048,
  412. publicKeyEncoding: {
  413. type: 'pkcs1',
  414. format: 'pem'
  415. },
  416. privateKeyEncoding: {
  417. type: 'pkcs1',
  418. format: 'pem',
  419. cipher: 'aes-256-cbc',
  420. passphrase: secret
  421. }
  422. })
  423. await knex('settings').insert([
  424. {
  425. key: 'auth',
  426. value: {
  427. audience: 'urn:wiki.js',
  428. tokenExpiration: '30m',
  429. tokenRenewal: '14d',
  430. certs: {
  431. jwk: pem2jwk(certs.publicKey),
  432. public: certs.publicKey,
  433. private: certs.privateKey
  434. },
  435. secret,
  436. rootAdminUserId: userAdminId,
  437. guestUserId: userGuestId
  438. }
  439. },
  440. {
  441. key: 'flags',
  442. value: {
  443. experimental: false,
  444. authDebug: false,
  445. sqlLog: false
  446. }
  447. },
  448. {
  449. key: 'icons',
  450. value: {
  451. fa: {
  452. isActive: true,
  453. config: {
  454. version: 6,
  455. license: 'free',
  456. token: ''
  457. }
  458. },
  459. la: {
  460. isActive: true
  461. }
  462. }
  463. },
  464. {
  465. key: 'mail',
  466. value: {
  467. senderName: '',
  468. senderEmail: '',
  469. host: '',
  470. port: 465,
  471. name: '',
  472. secure: true,
  473. verifySSL: true,
  474. user: '',
  475. pass: '',
  476. useDKIM: false,
  477. dkimDomainName: '',
  478. dkimKeySelector: '',
  479. dkimPrivateKey: ''
  480. }
  481. },
  482. {
  483. key: 'security',
  484. value: {
  485. corsConfig: '',
  486. corsMode: 'OFF',
  487. cspDirectives: '',
  488. disallowFloc: true,
  489. disallowIframe: true,
  490. disallowOpenRedirect: true,
  491. enforceCsp: false,
  492. enforceHsts: false,
  493. enforceSameOriginReferrerPolicy: true,
  494. forceAssetDownload: true,
  495. hstsDuration: 0,
  496. trustProxy: false,
  497. authJwtAudience: 'urn:wiki.js',
  498. authJwtExpiration: '30m',
  499. authJwtRenewablePeriod: '14d',
  500. uploadMaxFileSize: 10485760,
  501. uploadMaxFiles: 20,
  502. uploadScanSVG: true
  503. }
  504. },
  505. {
  506. key: 'update',
  507. value: {
  508. lastCheckedAt: null,
  509. version: null,
  510. versionDate: null
  511. }
  512. },
  513. {
  514. key: 'userDefaults',
  515. value: {
  516. timezone: 'America/New_York',
  517. dateFormat: 'YYYY-MM-DD',
  518. timeFormat: '12h'
  519. }
  520. }
  521. ])
  522. // -> DEFAULT SITE
  523. await knex('sites').insert({
  524. id: siteId,
  525. hostname: '*',
  526. isEnabled: true,
  527. config: {
  528. title: 'My Wiki Site',
  529. description: '',
  530. company: '',
  531. contentLicense: '',
  532. footerExtra: '',
  533. pageExtensions: ['md', 'html', 'txt'],
  534. pageCasing: true,
  535. discoverable: false,
  536. defaults: {
  537. tocDepth: {
  538. min: 1,
  539. max: 2
  540. }
  541. },
  542. features: {
  543. ratings: false,
  544. ratingsMode: 'off',
  545. comments: false,
  546. contributions: false,
  547. profile: true,
  548. reasonForChange: 'required',
  549. search: true
  550. },
  551. logoText: true,
  552. sitemap: true,
  553. robots: {
  554. index: true,
  555. follow: true
  556. },
  557. authStrategies: [{ id: authModuleId, order: 0, isVisible: true }],
  558. locales: {
  559. primary: 'en',
  560. active: ['en']
  561. },
  562. assets: {
  563. logo: false,
  564. logoExt: 'svg',
  565. favicon: false,
  566. faviconExt: 'svg',
  567. loginBg: false
  568. },
  569. editors: {
  570. asciidoc: {
  571. isActive: true,
  572. config: {}
  573. },
  574. markdown: {
  575. isActive: true,
  576. config: {
  577. allowHTML: true,
  578. kroki: false,
  579. krokiServerUrl: 'https://kroki.io',
  580. latexEngine: 'katex',
  581. lineBreaks: true,
  582. linkify: true,
  583. multimdTable: true,
  584. plantuml: false,
  585. plantumlServerUrl: 'https://www.plantuml.com/plantuml/',
  586. quotes: 'english',
  587. tabWidth: 2,
  588. typographer: false,
  589. underline: true
  590. }
  591. },
  592. wysiwyg: {
  593. isActive: true,
  594. config: {}
  595. }
  596. },
  597. theme: {
  598. dark: false,
  599. codeBlocksTheme: 'github-dark',
  600. colorPrimary: '#1976D2',
  601. colorSecondary: '#02C39A',
  602. colorAccent: '#FF9800',
  603. colorHeader: '#000000',
  604. colorSidebar: '#1976D2',
  605. injectCSS: '',
  606. injectHead: '',
  607. injectBody: '',
  608. contentWidth: 'full',
  609. sidebarPosition: 'left',
  610. tocPosition: 'right',
  611. showSharingMenu: true,
  612. showPrintBtn: true,
  613. baseFont: 'roboto',
  614. contentFont: 'roboto'
  615. },
  616. uploads: {
  617. conflictBehavior: 'overwrite',
  618. normalizeFilename: true
  619. }
  620. }
  621. })
  622. // -> DEFAULT GROUPS
  623. await knex('groups').insert([
  624. {
  625. id: groupAdminId,
  626. name: 'Administrators',
  627. permissions: JSON.stringify(['manage:system']),
  628. rules: JSON.stringify([]),
  629. isSystem: true
  630. },
  631. {
  632. id: groupGuestId,
  633. name: 'Guests',
  634. permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
  635. rules: JSON.stringify([
  636. {
  637. id: uuid(),
  638. name: 'Default Rule',
  639. roles: ['read:pages', 'read:assets', 'read:comments'],
  640. match: 'START',
  641. mode: 'DENY',
  642. path: '',
  643. locales: [],
  644. sites: []
  645. }
  646. ]),
  647. isSystem: true
  648. }
  649. ])
  650. // -> AUTHENTICATION MODULE
  651. await knex('authentication').insert({
  652. id: authModuleId,
  653. module: 'local',
  654. isEnabled: true,
  655. displayName: 'Local Authentication'
  656. })
  657. // -> USERS
  658. await knex('users').insert([
  659. {
  660. id: userAdminId,
  661. email: process.env.ADMIN_EMAIL ?? 'admin@example.com',
  662. auth: {
  663. [authModuleId]: {
  664. password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
  665. mustChangePwd: false, // TODO: Revert to true (below) once change password flow is implemented
  666. // mustChangePwd: !process.env.ADMIN_PASS,
  667. restrictLogin: false,
  668. tfaRequired: false,
  669. tfaSecret: ''
  670. }
  671. },
  672. name: 'Administrator',
  673. isSystem: false,
  674. isActive: true,
  675. isVerified: true,
  676. meta: {
  677. location: '',
  678. jobTitle: '',
  679. pronouns: ''
  680. },
  681. prefs: {
  682. timezone: 'America/New_York',
  683. dateFormat: 'YYYY-MM-DD',
  684. timeFormat: '12h',
  685. appearance: 'site',
  686. cvd: 'none'
  687. }
  688. },
  689. {
  690. id: userGuestId,
  691. email: 'guest@example.com',
  692. auth: {},
  693. name: 'Guest',
  694. isSystem: true,
  695. isActive: true,
  696. isVerified: true,
  697. meta: {},
  698. prefs: {
  699. timezone: 'America/New_York',
  700. dateFormat: 'YYYY-MM-DD',
  701. timeFormat: '12h',
  702. appearance: 'site',
  703. cvd: 'none'
  704. }
  705. }
  706. ])
  707. await knex('userGroups').insert([
  708. {
  709. userId: userAdminId,
  710. groupId: groupAdminId
  711. },
  712. {
  713. userId: userGuestId,
  714. groupId: groupGuestId
  715. }
  716. ])
  717. // -> STORAGE MODULE
  718. await knex('storage').insert({
  719. module: 'db',
  720. siteId,
  721. isEnabled: true,
  722. contentTypes: {
  723. activeTypes: ['pages', 'images', 'documents', 'others', 'large'],
  724. largeThreshold: '5MB'
  725. },
  726. assetDelivery: {
  727. streaming: true,
  728. directAccess: false
  729. },
  730. versioning: {
  731. enabled: false
  732. },
  733. state: {
  734. current: 'ok'
  735. }
  736. })
  737. // -> SCHEDULED JOBS
  738. await knex('jobSchedule').insert([
  739. {
  740. task: 'checkVersion',
  741. cron: '0 0 * * *',
  742. type: 'system'
  743. },
  744. {
  745. task: 'cleanJobHistory',
  746. cron: '5 0 * * *',
  747. type: 'system'
  748. },
  749. {
  750. task: 'updateLocales',
  751. cron: '0 0 * * *',
  752. type: 'system'
  753. }
  754. ])
  755. await knex('jobLock').insert({
  756. key: 'cron',
  757. lastCheckedBy: 'init',
  758. lastCheckedAt: DateTime.utc().minus({ hours: 1 }).toISO()
  759. })
  760. WIKI.logger.info('Completed 3.0.0 database migration.')
  761. }
  762. export function down (knex) { }