From fb7413dba4e0c2ef0500310f379490d8f05138c3 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 29 Sep 2023 17:59:17 +0200 Subject: [PATCH] Added migration to the new parenting schema --- ...ArchiveAt2.js => cleanArchiveAtCurrent.js} | 4 +- .../migrations/archive/migrateArchive.js | 7 +- .../migrations/archive/migrateArchive2To3.js | 16 +++++ .../migrations/server/dbv3/dbv3.test.ts | 69 +++++++++++++++++++ app/imports/migrations/server/dbv3/dbv3.ts | 48 +++++++++++++ app/package.json | 2 +- app/tsconfig.json | 1 + 7 files changed, 142 insertions(+), 5 deletions(-) rename app/imports/migrations/archive/{cleanArchiveAt2.js => cleanArchiveAtCurrent.js} (84%) create mode 100644 app/imports/migrations/archive/migrateArchive2To3.js create mode 100644 app/imports/migrations/server/dbv3/dbv3.test.ts create mode 100644 app/imports/migrations/server/dbv3/dbv3.ts diff --git a/app/imports/migrations/archive/cleanArchiveAt2.js b/app/imports/migrations/archive/cleanArchiveAtCurrent.js similarity index 84% rename from app/imports/migrations/archive/cleanArchiveAt2.js rename to app/imports/migrations/archive/cleanArchiveAtCurrent.js index 25392d58..b4483d82 100644 --- a/app/imports/migrations/archive/cleanArchiveAt2.js +++ b/app/imports/migrations/archive/cleanArchiveAtCurrent.js @@ -1,6 +1,6 @@ -import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -export default function cleanAt2(archive) { +export default function cleanArchiveAtCurrent(archive) { archive.properties = archive.properties.map(prop => { let cleanProp = prop; try { diff --git a/app/imports/migrations/archive/migrateArchive.js b/app/imports/migrations/archive/migrateArchive.js index 7ff21b3d..ff47c1a3 100644 --- a/app/imports/migrations/archive/migrateArchive.js +++ b/app/imports/migrations/archive/migrateArchive.js @@ -1,6 +1,7 @@ import migrateTo1 from './migrateArchiveTo1'; import migrate1To2 from './migrateArchive1To2'; -import cleanAt2 from './cleanArchiveAt2'; +import migrate2To3 from './migrateArchive2To3'; +import cleanAtCurrent from './cleanArchiveAtCurrent'; /* eslint no-fallthrough: "off" -- Using switch fallthrough to run all migration steps after the current version of the file. */ @@ -15,7 +16,9 @@ export default function migrateArchive(archive) { migrate1To2(archive); // V2 of DiceCloud, Schema version 2 case 2: - cleanAt2(archive); + migrate2To3(archive); + case 3: + cleanAtCurrent(archive); break; default: throw 'Archive version not supported'; diff --git a/app/imports/migrations/archive/migrateArchive2To3.js b/app/imports/migrations/archive/migrateArchive2To3.js new file mode 100644 index 00000000..785d271a --- /dev/null +++ b/app/imports/migrations/archive/migrateArchive2To3.js @@ -0,0 +1,16 @@ +export default function migrate2To3(archive) { + archive.properties = archive.properties.map(prop => { + try { + prop.root = prop.ancestors[0]; + if (!prop.root) { + throw 'Property has no root ancestor, will become orphaned' + } + if (prop.parent?.collection === 'creatureProperties') { + prop.parentId = prop.parent.id; + } + } catch (e) { + console.warn('Property migration 2 -> 3 failed: ', { propId: prop._id, error: e.message || e.reason || e.toString() }); + } + return prop; + }); +} diff --git a/app/imports/migrations/server/dbv3/dbv3.test.ts b/app/imports/migrations/server/dbv3/dbv3.test.ts new file mode 100644 index 00000000..f76d00ff --- /dev/null +++ b/app/imports/migrations/server/dbv3/dbv3.test.ts @@ -0,0 +1,69 @@ +import { migrateCollection } from './dbv3' +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; +import { assert } from 'chai'; + +describe('dbv3 Migrate parenting structure', function () { + // We are going to be adding malformed docs to the collection, so allow any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const collection = CreatureProperties as Mongo.Collection; + + this.beforeAll(async function () { + // Add only the required properties + await collection.removeAsync({}); + // Add a property and a child as if they were in schema v2 + // Use raw collection to bypass all schemas and validation + await collection.rawCollection().insertMany([ + { + '_id': 'classId', + 'type': 'class', + 'parent': { 'collection': 'creatures', 'id': 'creatureId' }, + 'ancestors': [ + { 'collection': 'creatures', 'id': 'creatureId' } + ], + 'order': 0, + 'tags': [], + }, { + '_id': 'noteId', + 'type': 'note', + 'parent': { 'collection': 'creatureProperties', 'id': 'classId' }, + 'ancestors': [ + { 'collection': 'creatures', 'id': 'creatureId' }, + { 'collection': 'creatureProperties', 'id': 'classId' } + ], + 'order': 1, + 'tags': [], + }, + ]); + }); + + this.afterAll(async function () { + // Remove all the properties + await collection.removeAsync({}); + }); + + it('Migrates parenting to the new schema', async function () { + // Migrate the collection + await migrateCollection('creatureProperties'); + + // Get the new documents + const parentDoc = await collection.findOneAsync('classId'); + const childDoc = await collection.findOneAsync('noteId'); + + assert.deepEqual( + parentDoc.root, { collection: 'creatures', id: 'creatureId' }, + 'parent root should be the creature' + ); + assert.deepEqual( + childDoc.root, { collection: 'creatures', id: 'creatureId' }, + 'child root should be the creature' + ); + + assert.doesNotHaveAnyKeys(parentDoc, ['parentId'], 'Parent should not have parentId set'); + assert.equal(childDoc.parentId, 'classId', 'child parentId should match parent\'s id'); + + assert.equal(parentDoc.left, 0, 'Parent left should be its old order'); + assert.equal(childDoc.left, 1, 'Child left should be its old order'); + + }); + +}); diff --git a/app/imports/migrations/server/dbv3/dbv3.ts b/app/imports/migrations/server/dbv3/dbv3.ts new file mode 100644 index 00000000..8962a874 --- /dev/null +++ b/app/imports/migrations/server/dbv3/dbv3.ts @@ -0,0 +1,48 @@ +import { Migrations } from 'meteor/percolate:migrations'; + +// Git version 2.0.59 +// Database version 3 +Migrations.add({ + version: 3, + name: 'Separates creature property tags from library tags', + + up() { + console.log('migrating up library nodes 2 -> 3'); + migrateCollection('libraryNodes'); + console.log('migrating up creature props 2 -> 3'); + migrateCollection('creatureProperties'); + console.log('New schema fields added, if it was done correctly remove the old fields manually'); + }, + + down() { + throw 'Migrating from version 3 down to version 2 is not supported' + }, + +}); + +export function migrateCollection(collectionName: string) { + // @ts-expect-error Collection.get is not defined + const collection = Mongo.Collection.get(collectionName); + // Copy the parent id field and the root ancestor to the new structure + // Using the mongo aggregation API + return collection.rawCollection().updateMany({}, [ + { + $addFields: { + 'root': { $arrayElemAt: ['$ancestors', 0] }, + 'parentId': { + // Parent ID must refer to a document in the same collection, so remove the parent ID + // if the parent reference refers to a different collection + $cond: { + if: { $eq: [collectionName, '$parent.collection'] }, + then: '$parent.id', + else: '$$REMOVE', + } + }, + // Set left and right to current order so that order is maintained on the first re-build + // of the tree structure + 'left': '$order', + 'right': '$order', + } + }, + ]); +} diff --git a/app/package.json b/app/package.json index 730727ba..bb5233dd 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dicecloud", - "version": "2.0.57", + "version": "2.0.59", "description": "Unofficial Online Realtime D&D 5e App", "license": "GPL-3.0", "repository": { diff --git a/app/tsconfig.json b/app/tsconfig.json index 4a00a2dc..e6cc6b2b 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -10,6 +10,7 @@ "preserveSymlinks": true, "allowJs": true, "checkJs": true, + "outDir": "build", "paths": { "/*": [ "./*"