adapt-authoring-content 3.2.1 → 3.2.3
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/lib/ContentModule.js
CHANGED
|
@@ -2,7 +2,7 @@ import { AbstractApiModule } from 'adapt-authoring-api'
|
|
|
2
2
|
import { Hook, stringifyValues } from 'adapt-authoring-core'
|
|
3
3
|
import { createObjectId, parseObjectId } from 'adapt-authoring-mongodb'
|
|
4
4
|
import { ObjectId } from 'mongodb'
|
|
5
|
-
import { ContentTree, buildAssetUsagePipeline, computeSortOrderOps, contentTypeToSchemaName, extractAssetIds, formatFriendlyId, parseMaxSeq } from './utils.js'
|
|
5
|
+
import { ContentTree, buildAssetUsagePipeline, computeSortOrderOps, contentTypeToSchemaName, extractAssetIds, fieldsToProjection, formatFriendlyId, parseMaxSeq } from './utils.js'
|
|
6
6
|
/**
|
|
7
7
|
* Module which handles course content
|
|
8
8
|
* @memberof content
|
|
@@ -571,6 +571,8 @@ class ContentModule extends AbstractApiModule {
|
|
|
571
571
|
if (originalDoc._courseId?.toString() !== payloads[0]._courseId?.toString()) {
|
|
572
572
|
await this.updateEnabledPlugins(payloads[0])
|
|
573
573
|
}
|
|
574
|
+
// place the clone at its requested _sortOrder and renumber siblings (no-op for course/config)
|
|
575
|
+
await this.updateSortOrder(payloads[0], payloads[0])
|
|
574
576
|
|
|
575
577
|
return payloads[0]
|
|
576
578
|
}
|
|
@@ -683,10 +685,28 @@ class ContentModule extends AbstractApiModule {
|
|
|
683
685
|
if (ifModifiedSince && lastModified <= ifModifiedSince) {
|
|
684
686
|
return res.status(304).end()
|
|
685
687
|
}
|
|
688
|
+
const treeFields = [
|
|
689
|
+
'_id',
|
|
690
|
+
'_parentId',
|
|
691
|
+
'_courseId',
|
|
692
|
+
'_type',
|
|
693
|
+
'_sortOrder',
|
|
694
|
+
'title',
|
|
695
|
+
'displayTitle',
|
|
696
|
+
'_friendlyId',
|
|
697
|
+
'_component',
|
|
698
|
+
'_layout',
|
|
699
|
+
'_menu',
|
|
700
|
+
'_theme',
|
|
701
|
+
'_enabledPlugins',
|
|
702
|
+
'_colorLabel',
|
|
703
|
+
'heroImage',
|
|
704
|
+
'updatedAt'
|
|
705
|
+
]
|
|
686
706
|
const items = await this.find(
|
|
687
707
|
{ _courseId },
|
|
688
708
|
{ validate: false },
|
|
689
|
-
{ projection:
|
|
709
|
+
{ projection: fieldsToProjection(treeFields) }
|
|
690
710
|
)
|
|
691
711
|
const tree = new ContentTree(items)
|
|
692
712
|
res.set('Last-Modified', lastModified.toUTCString())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a MongoDB inclusion projection from a list of field names, mapping
|
|
3
|
+
* each to `1` (e.g. `['_id', 'title']` -> `{ _id: 1, title: 1 }`). Lets a
|
|
4
|
+
* handler declare its projected fields as a readable one-per-line array.
|
|
5
|
+
* @param {Array<string>} fields Field names to include
|
|
6
|
+
* @return {Object} Inclusion projection
|
|
7
|
+
*/
|
|
8
|
+
export default function fieldsToProjection (fields) {
|
|
9
|
+
return Object.fromEntries((fields ?? []).map(field => [field, 1]))
|
|
10
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -3,5 +3,6 @@ export { default as buildAssetUsagePipeline } from './utils/buildAssetUsagePipel
|
|
|
3
3
|
export { default as computeSortOrderOps } from './utils/computeSortOrderOps.js'
|
|
4
4
|
export { extractAssetIds } from './utils/extractAssetIds.js'
|
|
5
5
|
export { default as contentTypeToSchemaName } from './utils/contentTypeToSchemaName.js'
|
|
6
|
+
export { default as fieldsToProjection } from './utils/fieldsToProjection.js'
|
|
6
7
|
export { default as formatFriendlyId } from './utils/formatFriendlyId.js'
|
|
7
8
|
export { default as parseMaxSeq } from './utils/parseMaxSeq.js'
|
package/package.json
CHANGED
|
@@ -384,6 +384,7 @@ describe('ContentModule', () => {
|
|
|
384
384
|
}),
|
|
385
385
|
getSchema: mock.fn(async () => ({})),
|
|
386
386
|
updateEnabledPlugins: mock.fn(async () => {}),
|
|
387
|
+
updateSortOrder: mock.fn(async () => {}),
|
|
387
388
|
preCloneHook: { invoke: mock.fn(async () => {}) },
|
|
388
389
|
preInsertHook: { invoke: mock.fn(async () => {}) },
|
|
389
390
|
postInsertHook: { invoke: mock.fn(async () => {}) },
|
|
@@ -460,6 +461,25 @@ describe('ContentModule', () => {
|
|
|
460
461
|
assert.equal(result._type, 'course')
|
|
461
462
|
})
|
|
462
463
|
|
|
464
|
+
it('should renumber siblings for the cloned root so it lands at its _sortOrder', async () => {
|
|
465
|
+
const { inst } = createCloneInstance()
|
|
466
|
+
|
|
467
|
+
const items = [
|
|
468
|
+
{ _id: COURSE_OID, _type: 'course', _courseId: COURSE_OID },
|
|
469
|
+
{ _id: PAGE_OID, _type: 'page', _parentId: COURSE_OID, _courseId: COURSE_OID }
|
|
470
|
+
]
|
|
471
|
+
const tree = new ContentTree(items)
|
|
472
|
+
const parent = { _id: COURSE_OID, _type: 'course', _courseId: COURSE_OID }
|
|
473
|
+
const result = await ContentModule.prototype.clone.call(inst, USER_OID, PAGE_OID, COURSE_OID, { _sortOrder: 1 }, { tree, parent })
|
|
474
|
+
|
|
475
|
+
assert.equal(inst.updateSortOrder.mock.callCount(), 1)
|
|
476
|
+
const [item, updateData] = inst.updateSortOrder.mock.calls[0].arguments
|
|
477
|
+
// called with the root payload (truthy updateData triggers the splice/renumber path)
|
|
478
|
+
assert.equal(item, result)
|
|
479
|
+
assert.ok(updateData)
|
|
480
|
+
assert.equal(item._sortOrder, 1)
|
|
481
|
+
})
|
|
482
|
+
|
|
463
483
|
it('should remap parent IDs correctly', async () => {
|
|
464
484
|
const { inst, mongodb } = createCloneInstance()
|
|
465
485
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fieldsToProjection from '../lib/utils/fieldsToProjection.js'
|
|
4
|
+
|
|
5
|
+
describe('fieldsToProjection', () => {
|
|
6
|
+
it('maps each field name to 1', () => {
|
|
7
|
+
assert.deepEqual(fieldsToProjection(['_id', 'title']), { _id: 1, title: 1 })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('returns an empty projection for an empty array', () => {
|
|
11
|
+
assert.deepEqual(fieldsToProjection([]), {})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('treats a missing argument as empty', () => {
|
|
15
|
+
assert.deepEqual(fieldsToProjection(), {})
|
|
16
|
+
assert.deepEqual(fieldsToProjection(undefined), {})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('collapses duplicate field names to a single key', () => {
|
|
20
|
+
assert.deepEqual(fieldsToProjection(['_id', '_id', 'title']), { _id: 1, title: 1 })
|
|
21
|
+
})
|
|
22
|
+
})
|