adapt-authoring-adaptframework 1.12.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5,4 +5,3 @@
5
5
  export { default } from './lib/AdaptFrameworkModule.js'
6
6
  export { default as AdaptFrameworkBuild } from './lib/AdaptFrameworkBuild.js'
7
7
  export { default as AdaptFrameworkImport } from './lib/AdaptFrameworkImport.js'
8
- export { default as AdaptFrameworkUtils } from './lib/AdaptFrameworkUtils.js'
@@ -1,8 +1,8 @@
1
1
  import _ from 'lodash'
2
- import { App, Hook } from 'adapt-authoring-core'
2
+ import { App, Hook, ensureDir, writeJson } from 'adapt-authoring-core'
3
3
  import { createWriteStream } from 'fs'
4
4
  import AdaptCli from 'adapt-cli'
5
- import FWUtils from './AdaptFrameworkUtils.js'
5
+ import { log, logDir, logMemory, copyFrameworkSource } from './utils.js'
6
6
  import fs from 'fs/promises'
7
7
  import path from 'upath'
8
8
  import semver from 'semver'
@@ -152,17 +152,6 @@ class AdaptFrameworkBuild {
152
152
  }
153
153
 
154
154
  /**
155
- * Makes sure the directory exists
156
- * @param {string} dir
157
- */
158
- async ensureDir (dir) {
159
- try {
160
- await fs.mkdir(dir, { recursive: true })
161
- } catch (e) {
162
- if (e.code !== 'EEXIST') throw e
163
- }
164
- }
165
-
166
155
  /**
167
156
  * Runs the Adapt framework build tools to generate a course build
168
157
  * @return {Promise} Resolves with the output directory
@@ -182,19 +171,19 @@ class AdaptFrameworkBuild {
182
171
 
183
172
  const cacheDir = path.join(framework.getConfig('buildDir'), 'cache')
184
173
 
185
- await this.ensureDir(this.dir)
186
- await this.ensureDir(this.buildDir)
187
- await this.ensureDir(cacheDir)
174
+ await ensureDir(this.dir)
175
+ await ensureDir(this.buildDir)
176
+ await ensureDir(cacheDir)
188
177
 
189
- FWUtils.logDir('dir', this.dir)
190
- FWUtils.logDir('buildDir', this.buildDir)
191
- FWUtils.logDir('cacheDir', this.cacheDir)
178
+ logDir('dir', this.dir)
179
+ logDir('buildDir', this.buildDir)
180
+ logDir('cacheDir', this.cacheDir)
192
181
 
193
182
  await this.loadCourseData()
194
183
 
195
184
  await Promise.all([
196
185
  this.copyAssets(),
197
- FWUtils.copyFrameworkSource({
186
+ copyFrameworkSource({
198
187
  destDir: this.dir,
199
188
  enabledPlugins: this.enabledPlugins.map(p => p.name),
200
189
  linkNodeModules: !this.isExport
@@ -204,11 +193,11 @@ class AdaptFrameworkBuild {
204
193
 
205
194
  await this.writeContentJson()
206
195
 
207
- FWUtils.logDir('courseDir', this.courseDir)
196
+ logDir('courseDir', this.courseDir)
208
197
 
209
198
  if (!this.isExport) {
210
199
  try {
211
- FWUtils.logMemory()
200
+ logMemory()
212
201
  await AdaptCli.buildCourse({
213
202
  cwd: this.dir,
214
203
  sourceMaps: !this.isPublish,
@@ -216,9 +205,9 @@ class AdaptFrameworkBuild {
216
205
  cachePath: path.resolve(cacheDir, this.courseId),
217
206
  logger: { log: (...args) => App.instance.logger.log('debug', 'adapt-cli', ...args) }
218
207
  })
219
- FWUtils.logMemory()
208
+ logMemory()
220
209
  } catch (e) {
221
- FWUtils.logMemory()
210
+ logMemory()
222
211
  throw App.instance.errors.FW_CLI_BUILD_FAILED
223
212
  .setData(e)
224
213
  }
@@ -405,7 +394,7 @@ class AdaptFrameworkBuild {
405
394
  if (a.url) {
406
395
  return
407
396
  }
408
- await this.ensureDir(path.dirname(this.assetData.idMap[a._id]))
397
+ await ensureDir(path.dirname(this.assetData.idMap[a._id]))
409
398
  const inputStream = await assets.createFsWrapper(a).read(a)
410
399
  const outputStream = createWriteStream(this.assetData.idMap[a._id])
411
400
  inputStream.pipe(outputStream)
@@ -434,10 +423,10 @@ class AdaptFrameworkBuild {
434
423
  data.push(this.assetData)
435
424
  }
436
425
  return Promise.all(data.map(async ({ dir, fileName, data }) => {
437
- await this.ensureDir(dir)
426
+ await ensureDir(dir)
438
427
  const filepath = path.join(dir, fileName)
439
- const returnData = await FWUtils.writeJson(filepath, data)
440
- FWUtils.log('verbose', 'WRITE', filepath)
428
+ const returnData = await writeJson(filepath, data)
429
+ log('verbose', 'WRITE', filepath)
441
430
  return returnData
442
431
  }))
443
432
  }
@@ -1,4 +1,4 @@
1
- import { App, Hook, Utils } from 'adapt-authoring-core'
1
+ import { App, Hook, spawn, readJson, writeJson } from 'adapt-authoring-core'
2
2
  import fs from 'fs/promises'
3
3
  import { glob } from 'glob'
4
4
  import octopus from 'adapt-octopus'
@@ -6,7 +6,7 @@ import path from 'upath'
6
6
  import { randomBytes } from 'node:crypto'
7
7
  import semver from 'semver'
8
8
  import { unzip } from 'zipper'
9
- import FWUtils from './AdaptFrameworkUtils.js'
9
+ import { log, logDir, getImportSummary, getImportContentCounts } from './utils.js'
10
10
 
11
11
  import ComponentTransform from './migrations/component.js'
12
12
  import ConfigTransform from './migrations/config.js'
@@ -255,8 +255,8 @@ class AdaptFrameworkImport {
255
255
  */
256
256
  this.jsonschema = jsonschema
257
257
 
258
- FWUtils.log('debug', 'IMPORT_USER', this.userId)
259
- FWUtils.log('debug', 'IMPORT_SETTINGS', JSON.stringify(this.settings, null, 2))
258
+ log('debug', 'IMPORT_USER', this.userId)
259
+ log('debug', 'IMPORT_SETTINGS', JSON.stringify(this.settings, null, 2))
260
260
 
261
261
  const { isDryRun, importContent, importPlugins, migrateContent } = this.settings
262
262
  const tasks = [
@@ -323,11 +323,11 @@ class AdaptFrameworkImport {
323
323
  this.framework.log('error', e)
324
324
  throw (e?.statusCode ? e : App.instance.errors.FW_IMPORT_INVALID_COURSE.setData({ reason: e.message }))
325
325
  }
326
- FWUtils.logDir('unzipPath', this.path)
327
- FWUtils.logDir('coursePath', this.coursePath)
326
+ logDir('unzipPath', this.path)
327
+ logDir('coursePath', this.coursePath)
328
328
 
329
329
  try {
330
- /** @ignore */this.pkg = await FWUtils.readJson(`${this.path}/package.json`)
330
+ /** @ignore */this.pkg = await readJson(`${this.path}/package.json`)
331
331
  } catch (e) {
332
332
  throw App.instance.errors.FW_IMPORT_INVALID.setData({ reason: e.message })
333
333
  }
@@ -344,7 +344,7 @@ class AdaptFrameworkImport {
344
344
  this.statusReport.info.push({ code: 'MIGRATE_CONTENT', data })
345
345
  }
346
346
  await this.convertSchemas()
347
- FWUtils.log('debug', 'preparation tasks completed successfully')
347
+ log('debug', 'preparation tasks completed successfully')
348
348
  }
349
349
 
350
350
  /**
@@ -354,7 +354,7 @@ class AdaptFrameworkImport {
354
354
  async convertSchemas () {
355
355
  return octopus.runRecursive({
356
356
  cwd: this.path,
357
- logger: { log: (...args) => FWUtils.log('debug', ...args) }
357
+ logger: { log: (...args) => log('debug', ...args) }
358
358
  })
359
359
  }
360
360
 
@@ -369,11 +369,11 @@ class AdaptFrameworkImport {
369
369
  }
370
370
  try {
371
371
  const customStyle = await fs.readFile(customStylePath, 'utf8')
372
- const courseJson = await FWUtils.readJson(courseJsonPath)
373
- await FWUtils.writeJson(courseJsonPath, { customStyle, ...courseJson })
374
- FWUtils.log('info', 'patched course customStyle')
372
+ const courseJson = await readJson(courseJsonPath)
373
+ await writeJson(courseJsonPath, { customStyle, ...courseJson })
374
+ log('info', 'patched course customStyle')
375
375
  } catch (e) {
376
- FWUtils.log('warn', 'failed to patch course customStyle', e)
376
+ log('warn', 'failed to patch course customStyle', e)
377
377
  }
378
378
  }
379
379
 
@@ -383,13 +383,13 @@ class AdaptFrameworkImport {
383
383
  async patchThemeName () {
384
384
  try {
385
385
  const configJsonPath = `${this.coursePath}/config.json`
386
- const configJson = await FWUtils.readJson(configJsonPath)
386
+ const configJson = await readJson(configJsonPath)
387
387
  if (configJson._theme) return
388
388
  configJson._theme = Object.values(this.usedContentPlugins).find(p => p.type === 'theme').name
389
- await FWUtils.writeJson(configJsonPath, configJson)
390
- FWUtils.log('info', 'patched config _theme')
389
+ await writeJson(configJsonPath, configJson)
390
+ log('info', 'patched config _theme')
391
391
  } catch (e) {
392
- FWUtils.log('warn', 'failed to patch config _theme', e)
392
+ log('warn', 'failed to patch config _theme', e)
393
393
  }
394
394
  }
395
395
 
@@ -401,14 +401,14 @@ class AdaptFrameworkImport {
401
401
  this.assetData = []
402
402
  const metaFiles = await glob(`${this.langPath}/assets.json`, { absolute: true })
403
403
  if (metaFiles.length) { // process included asset metadata
404
- FWUtils.log('debug', 'processing metadata files', metaFiles)
404
+ log('debug', 'processing metadata files', metaFiles)
405
405
  await Promise.all(metaFiles.map(async f => {
406
- const metaJson = await FWUtils.readJson(f)
406
+ const metaJson = await readJson(f)
407
407
  Object.entries(metaJson).forEach(([filename, metadata]) => this.assetData.push({ filename, ...metadata }))
408
408
  }))
409
409
  } else { // process the file metadata manually
410
410
  const assetFiles = await glob(`${this.langPath}/*/*`, { absolute: true })
411
- FWUtils.log('debug', 'processing asset files manually', assetFiles.length)
411
+ log('debug', 'processing asset files manually', assetFiles.length)
412
412
  this.assetData.push(...assetFiles.map(f => Object.assign({}, { title: path.basename(f), filepath: f })))
413
413
  }
414
414
  const hasGlobalTags = !!this.tags.length
@@ -431,9 +431,9 @@ class AdaptFrameworkImport {
431
431
  }
432
432
  }
433
433
  await Promise.all(usedPluginPaths.map(async p => {
434
- const bowerJson = await FWUtils.readJson(`${p}/bower.json`)
434
+ const bowerJson = await readJson(`${p}/bower.json`)
435
435
  const { name, version, targetAttribute } = bowerJson
436
- FWUtils.log('debug', 'found plugin', name)
436
+ log('debug', 'found plugin', name)
437
437
  this.usedContentPlugins[path.basename(p)] = { name, path: p, version, targetAttribute, type: getPluginType(bowerJson) }
438
438
  }))
439
439
  this.contentJson.config._enabledPlugins = Object.keys(this.usedContentPlugins)
@@ -446,8 +446,8 @@ class AdaptFrameworkImport {
446
446
  async loadCourseData () {
447
447
  const files = await glob('**/*.json', { cwd: this.langPath, absolute: true, ignore: { ignored: p => p.name === 'assets.json' } })
448
448
  const mapped = await Promise.all(files.map(f => this.loadContentFile(f)))
449
- this.statusReport.info.push({ code: 'CONTENT_IMPORTED', data: FWUtils.getImportContentCounts(this.contentJson) })
450
- FWUtils.log('info', 'loaded course data successfully')
449
+ this.statusReport.info.push({ code: 'CONTENT_IMPORTED', data: getImportContentCounts(this.contentJson) })
450
+ log('info', 'loaded course data successfully')
451
451
  return mapped
452
452
  }
453
453
 
@@ -458,7 +458,7 @@ class AdaptFrameworkImport {
458
458
  async loadContentFile (filePath) {
459
459
  let contents
460
460
  try {
461
- contents = await FWUtils.readJson(filePath)
461
+ contents = await readJson(filePath)
462
462
  } catch (e) {
463
463
  if (e.constructor.name === 'SyntaxError') {
464
464
  throw App.instance.errors.FILE_SYNTAX_ERROR.setData({ path: filePath.replace(this.path, ''), message: e.message })
@@ -481,12 +481,12 @@ class AdaptFrameworkImport {
481
481
  contents.forEach(c => {
482
482
  this.contentJson.contentObjects[c._id] = c
483
483
  if (!c._type) {
484
- FWUtils.log('warn', App.instance.errors.FW_IMPORT_INVALID_CONTENT.setData({ item: c }))
484
+ log('warn', App.instance.errors.FW_IMPORT_INVALID_CONTENT.setData({ item: c }))
485
485
  this.statusReport.warn.push({ code: 'INVALID_CONTENT', data: c })
486
486
  }
487
487
  })
488
488
  }
489
- FWUtils.log('debug', 'LOAD_CONTENT', path.resolve(filePath))
489
+ log('debug', 'LOAD_CONTENT', path.resolve(filePath))
490
490
  }
491
491
 
492
492
  /**
@@ -494,7 +494,7 @@ class AdaptFrameworkImport {
494
494
  * @return {Promise}
495
495
  */
496
496
  async runGruntMigration (subTask, { outputDir, captureDir, outputFilePath }) {
497
- const output = await Utils.spawn({
497
+ const output = await spawn({
498
498
  cmd: 'npx',
499
499
  args: ['grunt', `migration:${subTask}`, `--outputdir=${outputDir}`, `--capturedir=${captureDir}`],
500
500
  cwd: this.frameworkPath ?? this.framework.path
@@ -517,16 +517,16 @@ class AdaptFrameworkImport {
517
517
  captureDir: path.join(`./${migrationId}-migrations`),
518
518
  outputFilePath: path.join(this.framework.path, 'migrations', `${migrationId}.txt`)
519
519
  }
520
- FWUtils.log('debug', 'MIGRATION_ID', migrationId)
521
- FWUtils.logDir('captureDir', opts.captureDir)
522
- FWUtils.logDir('outputDir', opts.outputDir)
520
+ log('debug', 'MIGRATION_ID', migrationId)
521
+ logDir('captureDir', opts.captureDir)
522
+ logDir('outputDir', opts.outputDir)
523
523
 
524
524
  await this.runGruntMigration('capture', opts)
525
525
  await this.runGruntMigration('migrate', opts)
526
526
 
527
527
  await fs.rm(path.join(this.framework.path, opts.captureDir), { recursive: true })
528
528
  } catch (error) {
529
- FWUtils.log('error', 'Migration process failed', error)
529
+ log('error', 'Migration process failed', error)
530
530
  throw App.instance.errors.FW_IMPORT_MIGRATION_FAILED.setData({ reason: error.message })
531
531
  }
532
532
  }
@@ -568,7 +568,7 @@ class AdaptFrameworkImport {
568
568
  if (course.tags) {
569
569
  course.tags = course.tags.map(t => existingTagMap[t])
570
570
  }
571
- FWUtils.log('debug', 'imported tags successfully')
571
+ log('debug', 'imported tags successfully')
572
572
  }
573
573
 
574
574
  /**
@@ -602,7 +602,7 @@ class AdaptFrameworkImport {
602
602
  }
603
603
  imagesImported++
604
604
  }))
605
- FWUtils.log('debug', 'imported course assets successfully')
605
+ log('debug', 'imported course assets successfully')
606
606
  this.statusReport.info.push({ code: 'ASSETS_IMPORTED_SUCCESSFULLY', data: { count: imagesImported } })
607
607
  }
608
608
 
@@ -632,7 +632,7 @@ class AdaptFrameworkImport {
632
632
  const { version: installedVersion, isLocalInstall } = installedP
633
633
  if (semver.lt(importVersion, installedVersion)) {
634
634
  this.statusReport.info.push({ code: 'PLUGIN_INSTALL_MIGRATING', data: { name: p, installedVersion, importVersion } })
635
- FWUtils.log('debug', `migrating '${p}@${importVersion}' during import, installed version is newer (${installedVersion})`)
635
+ log('debug', `migrating '${p}@${importVersion}' during import, installed version is newer (${installedVersion})`)
636
636
  this.pluginsToMigrate.push(p)
637
637
  return
638
638
  }
@@ -642,12 +642,12 @@ class AdaptFrameworkImport {
642
642
  }
643
643
  if (semver.eq(importVersion, installedVersion)) {
644
644
  this.statusReport.info.push({ code: 'PLUGIN_INSTALL_NOT_NEWER', data: { name: p, installedVersion, importVersion } })
645
- FWUtils.log('debug', `not updating '${p}@${importVersion}' during import, installed version is equal to (${installedVersion})`)
645
+ log('debug', `not updating '${p}@${importVersion}' during import, installed version is equal to (${installedVersion})`)
646
646
  return
647
647
  }
648
648
  if (!isLocalInstall) {
649
649
  this.statusReport.warn.push({ code: 'MANAGED_PLUGIN_INSTALL_SKIPPED', data: { name: p, installedVersion, importVersion } })
650
- FWUtils.log('debug', `cannot update '${p}' during import, plugin managed via UI`)
650
+ log('debug', `cannot update '${p}' during import, plugin managed via UI`)
651
651
  }
652
652
  pluginsToUpdate.push(p)
653
653
  })
@@ -670,10 +670,10 @@ class AdaptFrameworkImport {
670
670
  }
671
671
  // try and infer a targetAttribute if there isn't one
672
672
  const pluginBowerPath = path.join(this.usedContentPlugins[p].path, 'bower.json')
673
- const bowerJson = await FWUtils.readJson(pluginBowerPath)
673
+ const bowerJson = await readJson(pluginBowerPath)
674
674
  if (!bowerJson.targetAttribute) {
675
675
  bowerJson.targetAttribute = `_${bowerJson.component || bowerJson.extension || bowerJson.menu || bowerJson.theme}`
676
- await FWUtils.writeJson(pluginBowerPath, bowerJson)
676
+ await writeJson(pluginBowerPath, bowerJson)
677
677
  }
678
678
  if (!this.settings.isDryRun) {
679
679
  const [pluginData] = await this.contentplugin.installPlugins([[p, this.usedContentPlugins[p].path]], { strict: true })
@@ -684,9 +684,9 @@ class AdaptFrameworkImport {
684
684
  this.statusReport.info.push({ code: 'INSTALL_PLUGIN', data: { name: p, version: bowerJson.version } })
685
685
  } catch (e) {
686
686
  if (e.code === 'EEXIST') {
687
- FWUtils.log('warn', 'PLUGIN_ALREADY_EXISTS', p)
687
+ log('warn', 'PLUGIN_ALREADY_EXISTS', p)
688
688
  } else {
689
- FWUtils.log('error', 'PLUGIN_IMPORT_FAILED', p, e)
689
+ log('error', 'PLUGIN_IMPORT_FAILED', p, e)
690
690
  errors.push({ plugin: p, error: e.data?.errors?.[0] ?? e })
691
691
  }
692
692
  }
@@ -699,7 +699,7 @@ class AdaptFrameworkImport {
699
699
  this.componentNameMap = Object.values({ ...this.installedPlugins, ...this.newContentPlugins }).reduce((m, v) => {
700
700
  return { ...m, [v.targetAttribute.slice(1)]: v.name }
701
701
  }, {})
702
- FWUtils.log('debug', 'imported course plugins successfully')
702
+ log('debug', 'imported course plugins successfully')
703
703
  }
704
704
 
705
705
  /**
@@ -744,7 +744,7 @@ class AdaptFrameworkImport {
744
744
  }
745
745
  }
746
746
  if (errors.length) throw App.instance.errors.FW_IMPORT_CONTENT_FAILED.setData({ errors })
747
- FWUtils.log('debug', 'imported course data successfully')
747
+ log('debug', 'imported course data successfully')
748
748
  }
749
749
 
750
750
  /**
@@ -790,7 +790,7 @@ class AdaptFrameworkImport {
790
790
  try {
791
791
  this.extractAssets(schema.built.properties, insertData)
792
792
  } catch (e) {
793
- FWUtils.log('error', `failed to extract asset data for attribute '${e.attribute}' of schema '${schemaName}', ${e}`)
793
+ log('error', `failed to extract asset data for attribute '${e.attribute}' of schema '${schemaName}', ${e}`)
794
794
  }
795
795
  insertData = await schema.sanitise(insertData)
796
796
  let doc
@@ -847,7 +847,7 @@ class AdaptFrameworkImport {
847
847
  * @return {Promise}
848
848
  */
849
849
  async generateSummary () {
850
- this.summary = await FWUtils.getImportSummary(this)
850
+ this.summary = await getImportSummary(this)
851
851
  }
852
852
 
853
853
  /**
@@ -902,10 +902,10 @@ class AdaptFrameworkImport {
902
902
 
903
903
  const restoreTasks = []
904
904
  for (const [pluginName, originalMetadata] of Object.entries(this.updatedContentPlugins)) {
905
- FWUtils.log('info', `restoring plugin '${pluginName}' to previous version ${originalMetadata.version}`)
905
+ log('info', `restoring plugin '${pluginName}' to previous version ${originalMetadata.version}`)
906
906
  restoreTasks.push(
907
907
  this.contentplugin.restorePluginFromBackup(pluginName)
908
- .catch(e => FWUtils.log('error', `failed to restore plugin '${pluginName}' from backup, ${e.message}`))
908
+ .catch(e => log('error', `failed to restore plugin '${pluginName}' from backup, ${e.message}`))
909
909
  )
910
910
  }
911
911
  return Promise.allSettled(restoreTasks)
@@ -1,9 +1,10 @@
1
- import { AbstractModule, Hook } from 'adapt-authoring-core'
1
+ import { AbstractModule, Hook, readJson } from 'adapt-authoring-core'
2
2
  import AdaptFrameworkBuild from './AdaptFrameworkBuild.js'
3
3
  import AdaptFrameworkImport from './AdaptFrameworkImport.js'
4
4
  import ApiDefs from './apidefs.js'
5
5
  import fs from 'fs/promises'
6
- import FWUtils from './AdaptFrameworkUtils.js'
6
+ import { getHandler, postHandler, importHandler, postUpdateHandler, getUpdateHandler } from './handlers.js'
7
+ import { runCliCommand } from './utils.js'
7
8
  import path from 'path'
8
9
  import semver from 'semver'
9
10
 
@@ -73,10 +74,10 @@ class AdaptFrameworkModule extends AbstractModule {
73
74
  }
74
75
 
75
76
  /**
76
- * Reference to AdaptFrameworkUtils#runCliCommand
77
+ * Reference to runCliCommand utility
77
78
  */
78
79
  get runCliCommand () {
79
- return FWUtils.runCliCommand
80
+ return runCliCommand
80
81
  }
81
82
 
82
83
  /**
@@ -132,7 +133,7 @@ class AdaptFrameworkModule extends AbstractModule {
132
133
  * @return {Promise}
133
134
  */
134
135
  async getManifestPlugins () {
135
- const manifest = await FWUtils.readJson(path.resolve(this.path, 'adapt.json'))
136
+ const manifest = await readJson(path.resolve(this.path, 'adapt.json'))
136
137
  return Object.entries(manifest.dependencies)
137
138
  }
138
139
 
@@ -220,7 +221,7 @@ class AdaptFrameworkModule extends AbstractModule {
220
221
  route: '/preview/:id/{*splat}',
221
222
  handlers: {
222
223
  get: (req, res, next) => { // fail silently
223
- FWUtils.getHandler(req, res, e => e ? res.status(e.statusCode || 500).end() : next())
224
+ getHandler(req, res, e => e ? res.status(e.statusCode || 500).end() : next())
224
225
  }
225
226
  }
226
227
  })
@@ -232,22 +233,22 @@ class AdaptFrameworkModule extends AbstractModule {
232
233
  this.apiRouter.addRoute(
233
234
  {
234
235
  route: '/preview/:id',
235
- handlers: { post: FWUtils.postHandler },
236
+ handlers: { post: postHandler },
236
237
  meta: ApiDefs.preview
237
238
  },
238
239
  {
239
240
  route: '/publish/:id',
240
- handlers: { post: FWUtils.postHandler, get: FWUtils.getHandler },
241
+ handlers: { post: postHandler, get: getHandler },
241
242
  meta: ApiDefs.publish
242
243
  },
243
244
  {
244
245
  route: '/import',
245
- handlers: { post: [FWUtils.importHandler] },
246
+ handlers: { post: [importHandler] },
246
247
  meta: ApiDefs.import
247
248
  },
248
249
  {
249
250
  route: '/export/:id',
250
- handlers: { post: FWUtils.postHandler, get: FWUtils.getHandler },
251
+ handlers: { post: postHandler, get: getHandler },
251
252
  meta: ApiDefs.export
252
253
  }
253
254
  )
@@ -262,7 +263,7 @@ class AdaptFrameworkModule extends AbstractModule {
262
263
  if (this.getConfig('enableUpdateApi')) {
263
264
  this.apiRouter.addRoute({
264
265
  route: '/update',
265
- handlers: { post: FWUtils.postUpdateHandler, get: FWUtils.getUpdateHandler },
266
+ handlers: { post: postUpdateHandler, get: getUpdateHandler },
266
267
  meta: ApiDefs.update
267
268
  })
268
269
  auth.secureRoute(`${this.apiRouter.path}/update`, 'get', ['update:adapt'])
@@ -0,0 +1,164 @@
1
+ import { App, toBoolean } from 'adapt-authoring-core'
2
+ import path from 'upath'
3
+ import semver from 'semver'
4
+ import { inferBuildAction, retrieveBuildData, slugifyTitle, log } from './utils.js'
5
+
6
+ /**
7
+ * Handles GET requests to the API
8
+ * @param {external:ExpressRequest} req
9
+ * @param {external:ExpressResponse} res
10
+ * @param {Function} next
11
+ * @return {Promise}
12
+ */
13
+ export async function getHandler (req, res, next) {
14
+ const action = inferBuildAction(req)
15
+ const id = req.params.id
16
+ let buildData
17
+ try {
18
+ buildData = await retrieveBuildData(id)
19
+ } catch (e) {
20
+ return next(e)
21
+ }
22
+ if (!buildData || new Date(buildData.expiresAt).getTime() < Date.now()) {
23
+ return next(App.instance.errors.FW_BUILD_NOT_FOUND.setData({ _id: id }))
24
+ }
25
+ if (action === 'publish' || action === 'export') {
26
+ res.set('content-disposition', `attachment; filename="${await slugifyTitle(buildData)}.zip"`)
27
+ return res.sendFile(path.resolve(buildData.location), e => e && next(e))
28
+ }
29
+ if (action === 'preview') {
30
+ if (!req.auth.user) {
31
+ return res.status(App.instance.errors.MISSING_AUTH_HEADER.statusCode).end()
32
+ }
33
+ const filePath = path.resolve(buildData.location, req.path.slice(req.path.indexOf(id) + id.length + 1) || 'index.html')
34
+ await res.sendFile(filePath, e => {
35
+ if (!e) return
36
+ if (e.code === 'ENOENT') e = App.instance.errors.NOT_FOUND.setData({ type: 'file', id: filePath })
37
+ next(e)
38
+ })
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Handles POST requests to the API
44
+ * @param {external:ExpressRequest} req
45
+ * @param {external:ExpressResponse} res
46
+ * @param {Function} next
47
+ * @return {Promise}
48
+ */
49
+ export async function postHandler (req, res, next) {
50
+ const framework = await App.instance.waitForModule('adaptframework')
51
+ const startTime = Date.now()
52
+ const action = inferBuildAction(req)
53
+ const courseId = req.params.id
54
+ const userId = req.auth.user._id.toString()
55
+
56
+ log('info', `running ${action} for course '${courseId}' initiated by ${userId}`)
57
+ try {
58
+ const { isPreview, buildData } = await framework.buildCourse({ action, courseId, userId })
59
+ const duration = Math.round((Date.now() - startTime) / 10) / 100
60
+ log('info', `finished ${action} for course '${courseId}' in ${duration} seconds`)
61
+ const urlRoot = isPreview ? framework.rootRouter.url : framework.apiRouter.url
62
+ res.json({
63
+ [`${action}_url`]: `${urlRoot}/${action}/${buildData._id}/`,
64
+ versions: buildData.versions
65
+ })
66
+ } catch (e) {
67
+ log('error', `failed to ${action} course '${courseId}'`)
68
+ return next(e)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Deals with an incoming course (supports both local zip and remote URL stream)
74
+ * @param {external:ExpressRequest} req
75
+ * @param {external:ExpressResponse} res
76
+ * @return {Promise}
77
+ */
78
+ async function handleImportFile (req, res) {
79
+ const [fw, middleware] = await App.instance.waitForModule('adaptframework', 'middleware')
80
+ const handler = req.get('Content-Type').indexOf('multipart/form-data') === 0
81
+ ? middleware.fileUploadParser
82
+ : middleware.urlUploadParser
83
+ return new Promise((resolve, reject) => {
84
+ handler(middleware.zipTypes, { maxFileSize: fw.getConfig('importMaxFileSize'), unzip: true })(req, res, e => e ? reject(e) : resolve())
85
+ })
86
+ }
87
+
88
+ /**
89
+ * Handles POST /import requests to the API
90
+ * @param {external:ExpressRequest} req
91
+ * @param {external:ExpressResponse} res
92
+ * @param {Function} next
93
+ * @return {Promise}
94
+ */
95
+ export async function importHandler (req, res, next) {
96
+ try {
97
+ const framework = await App.instance.waitForModule('adaptframework')
98
+ let importPath = req.body.importPath
99
+ if (req.get('Content-Type').indexOf('multipart/form-data') === 0) {
100
+ await handleImportFile(req, res)
101
+ const [course] = req.fileUpload.files.course
102
+ importPath = course.filepath
103
+ }
104
+ const importer = await framework.importCourse({
105
+ importPath,
106
+ userId: req.auth.user._id.toString(),
107
+ isDryRun: toBoolean(req.body.dryRun),
108
+ assetFolders: req.body.formAssetFolders,
109
+ tags: req.body.tags?.length > 0 ? req.body.tags?.split(',') : [],
110
+ importContent: toBoolean(req.body.importContent),
111
+ importPlugins: toBoolean(req.body.importPlugins),
112
+ migrateContent: toBoolean(req.body.migrateContent),
113
+ updatePlugins: toBoolean(req.body.updatePlugins)
114
+ })
115
+ res.json(importer.summary)
116
+ } catch (e) {
117
+ return next(e?.statusCode ? e : App.instance.errors.FW_IMPORT_FAILED.setData({ error: e }))
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Handles POST /update requests to the API
123
+ * @param {external:ExpressRequest} req
124
+ * @param {external:ExpressResponse} res
125
+ * @param {Function} next
126
+ * @return {Promise}
127
+ */
128
+ export async function postUpdateHandler (req, res, next) {
129
+ try {
130
+ log('info', 'running framework update')
131
+ const framework = await App.instance.waitForModule('adaptframework')
132
+ const previousVersion = framework.version
133
+ await framework.updateFramework(req.body.version)
134
+ const currentVersion = framework.version !== previousVersion ? framework.version : undefined
135
+ res.json({
136
+ from: previousVersion,
137
+ to: currentVersion
138
+ })
139
+ } catch (e) {
140
+ return next(e)
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Handles GET /update requests to the API
146
+ * @param {external:ExpressRequest} req
147
+ * @param {external:ExpressResponse} res
148
+ * @param {Function} next
149
+ * @return {Promise}
150
+ */
151
+ export async function getUpdateHandler (req, res, next) {
152
+ try {
153
+ const framework = await App.instance.waitForModule('adaptframework')
154
+ const current = framework.version
155
+ const latest = await framework.getLatestVersion()
156
+ res.json({
157
+ canBeUpdated: semver.gt(latest, current),
158
+ currentVersion: current,
159
+ latestCompatibleVersion: latest
160
+ })
161
+ } catch (e) {
162
+ return next(e)
163
+ }
164
+ }