3.0.0.mjs 30 KB

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