3.0.0.mjs 31 KB

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