Migrated insert prop methods to nested sets
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
|
||||
export default function getRootCreatureAncestor(property) {
|
||||
return Creatures.findOne(property.ancestors[0].id);
|
||||
if (property.root?.collection !== 'creatures') {
|
||||
throw 'Property does not have a root ancestor'
|
||||
}
|
||||
return Creatures.findOne(property.root.id);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
|
||||
import { getAncestry } from '/imports/api/parenting/parentingFunctions';
|
||||
import { fetchDocByRef, rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
|
||||
import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag';
|
||||
import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
||||
import { getHighestOrder } from '/imports/api/parenting/order';
|
||||
|
||||
const insertProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.insert',
|
||||
@@ -25,27 +23,23 @@ const insertProperty = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ creatureProperty, parentRef }) {
|
||||
// get the new ancestry for the properties
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
let rootCreature;
|
||||
const parentDoc = fetchDocByRef(parentRef);
|
||||
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
if (parentRef.collection === 'creatures') {
|
||||
rootCreature = parentDoc;
|
||||
} else if (parentRef.collection === 'creatureProperties') {
|
||||
rootCreature = getRootCreatureAncestor(parentDoc);
|
||||
creatureProperty.parentId = parentDoc._id;
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
creatureProperty.parent = parentRef;
|
||||
creatureProperty.ancestors = ancestors;
|
||||
creatureProperty.root = { collection: 'creatures', id: rootCreature._id };
|
||||
|
||||
return insertPropertyWork({
|
||||
property: creatureProperty,
|
||||
creature: rootCreature,
|
||||
});
|
||||
return insertPropertyWork(creatureProperty);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -77,18 +71,17 @@ const insertPropertyAsChildOfTag = new ValidatedMethod({
|
||||
},
|
||||
run({ creatureProperty, creatureId, tag, tagDefaultName }) {
|
||||
let parentRef = getParentRefByTag(creatureId, tag);
|
||||
let insertFolderFirst = false;
|
||||
|
||||
if (!parentRef) {
|
||||
// Use the creature as the parent and mark that we need to insert the folder first later
|
||||
var insertFolderFirst = true;
|
||||
insertFolderFirst = true;
|
||||
parentRef = { id: creatureId, collection: 'creatures' };
|
||||
}
|
||||
|
||||
// get the new ancestry for the properties
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
const parentDoc = fetchDocByRef(parentRef);
|
||||
if (parentRef.collection === 'creatures') {
|
||||
rootCreature = parentDoc;
|
||||
} else if (parentRef.collection === 'creatureProperties') {
|
||||
@@ -98,43 +91,34 @@ const insertPropertyAsChildOfTag = new ValidatedMethod({
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
const root = { collection: 'creatures', id: rootCreature._id };
|
||||
|
||||
// Add the folder first if we need to
|
||||
if (insertFolderFirst) {
|
||||
let order = getHighestOrder({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: parentRef.id,
|
||||
}) + 1;
|
||||
let id = CreatureProperties.insert({
|
||||
type: 'folder',
|
||||
name: tagDefaultName || (tag.charAt(0).toUpperCase() + tag.slice(1)),
|
||||
tags: [tag],
|
||||
parent: parentRef,
|
||||
ancestors: [parentRef],
|
||||
order,
|
||||
// parentId: undefined,
|
||||
root,
|
||||
});
|
||||
// Make the folder our new parent
|
||||
let newParentRef = { id, collection: 'creatureProperties' };
|
||||
ancestors = [parentRef, newParentRef];
|
||||
parentRef = newParentRef;
|
||||
creatureProperty.order = order + 1;
|
||||
parentRef = { id, collection: 'creatureProperties' };
|
||||
}
|
||||
|
||||
creatureProperty.parent = parentRef;
|
||||
creatureProperty.ancestors = ancestors;
|
||||
creatureProperty.root = root;
|
||||
creatureProperty.parentId = parentRef.id;
|
||||
|
||||
return insertPropertyWork({
|
||||
property: creatureProperty,
|
||||
creature: rootCreature,
|
||||
});
|
||||
return insertPropertyWork(creatureProperty);
|
||||
},
|
||||
});
|
||||
|
||||
export function insertPropertyWork({ property, creature }) {
|
||||
export function insertPropertyWork(property) {
|
||||
delete property._id;
|
||||
property.dirty = true;
|
||||
let _id = CreatureProperties.insert(property);
|
||||
// Tree structure changed by insert, reorder the tree
|
||||
rebuildNestedSets(CreatureProperties, creature._id);
|
||||
rebuildNestedSets(CreatureProperties, property.root.id);
|
||||
return _id;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,11 @@ import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
getAncestry,
|
||||
renewDocIds
|
||||
renewDocIds,
|
||||
fetchDocByRef,
|
||||
rebuildNestedSets,
|
||||
getFilter
|
||||
} from '/imports/api/parenting/parentingFunctions';
|
||||
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
|
||||
import { setDocToLastOrder } from '/imports/api/parenting/order';
|
||||
import { fetchDocByRef } from '/imports/api/parenting/parentingFunctions';
|
||||
import { union } from 'lodash';
|
||||
|
||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
@@ -30,19 +28,15 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
parentRef: {
|
||||
type: RefSchema,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ nodeIds, parentRef, order }) {
|
||||
run({ nodeIds, parentRef }) {
|
||||
// get the new ancestry for the properties
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
const parentDoc = fetchDocByRef(parentRef);
|
||||
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
@@ -55,34 +49,32 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// {libraryId: hasViewPermission}
|
||||
//let libraryPermissionMemoir = {};
|
||||
const root = { collection: 'creatures', id: rootCreature._id };
|
||||
const parentId = parentRef.id;
|
||||
|
||||
let node;
|
||||
nodeIds.forEach(nodeId => {
|
||||
// TODO: Check library view permission for each node before starting
|
||||
node = insertPropertyFromNode(nodeId, ancestors, order);
|
||||
node = insertPropertyFromNode(nodeId, root, parentId);
|
||||
});
|
||||
|
||||
// get one of the root inserted docs
|
||||
let rootId = node._id;
|
||||
|
||||
// Tree structure changed by inserts, reorder the tree
|
||||
rebuildNestedSets(CreatureProperties, rootCreature._id);
|
||||
// Return the docId of the last property, the inserted root property
|
||||
return rootId;
|
||||
|
||||
// get one of the root inserted docs
|
||||
const lastInsertedId = node?._id;
|
||||
return lastInsertedId;
|
||||
},
|
||||
});
|
||||
|
||||
function insertPropertyFromNode(nodeId, ancestors, order) {
|
||||
// Fetch the library node and its decendents, provided they have not been
|
||||
function insertPropertyFromNode(nodeId, root, parentId) {
|
||||
// Fetch the library node and its descendants, provided they have not been
|
||||
// removed
|
||||
// TODO: Check permission to read the library this node is in
|
||||
let node = LibraryNodes.findOne({
|
||||
_id: nodeId,
|
||||
removed: { $ne: true },
|
||||
});
|
||||
if (!node) {
|
||||
if (Meteor.isClient) return;
|
||||
if (Meteor.isClient) return {};
|
||||
else {
|
||||
throw new Meteor.Error(
|
||||
'Insert property from library failed',
|
||||
@@ -90,13 +82,12 @@ function insertPropertyFromNode(nodeId, ancestors, order) {
|
||||
);
|
||||
}
|
||||
}
|
||||
let oldParent = node.parent;
|
||||
|
||||
let nodes = LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
...getFilter.descendants(node),
|
||||
removed: { $ne: true },
|
||||
}).fetch();
|
||||
|
||||
|
||||
// The root node is first in the array of nodes
|
||||
// It must get the first generated ID to prevent flickering
|
||||
nodes = [node, ...nodes];
|
||||
@@ -109,31 +100,17 @@ function insertPropertyFromNode(nodeId, ancestors, order) {
|
||||
// set libraryNodeIds
|
||||
storeLibraryNodeReferences(nodes);
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
newAncestry: ancestors,
|
||||
oldParent,
|
||||
});
|
||||
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({
|
||||
docArray: nodes,
|
||||
collectionMap: { 'libraryNodes': 'creatureProperties' }
|
||||
});
|
||||
|
||||
// Order the root node
|
||||
if (order === undefined) {
|
||||
setDocToLastOrder({
|
||||
collection: CreatureProperties,
|
||||
doc: node,
|
||||
});
|
||||
} else {
|
||||
node.order = order;
|
||||
}
|
||||
// Mark root node as dirty
|
||||
node.dirty = true;
|
||||
|
||||
// Mark all nodes as dirty
|
||||
dirtyNodes(nodes);
|
||||
// Move the root node to the end of the order
|
||||
node.left = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
// Insert the creature properties
|
||||
CreatureProperties.batchInsert(nodes);
|
||||
@@ -147,12 +124,6 @@ function storeLibraryNodeReferences(nodes) {
|
||||
});
|
||||
}
|
||||
|
||||
function dirtyNodes(nodes) {
|
||||
nodes.forEach(node => {
|
||||
node.dirty = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Covert node references into actual nodes
|
||||
// TODO: check permissions for each library a reference node references
|
||||
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) {
|
||||
@@ -175,7 +146,6 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) {
|
||||
let referencedNode
|
||||
try {
|
||||
referencedNode = fetchDocByRef(node.ref);
|
||||
referencedNode.order = node.order;
|
||||
referencedNode.tags = union(node.tags, referencedNode.tags);
|
||||
// We are definitely replacing this node, so add it to the list
|
||||
visitedRefs.add(node._id);
|
||||
@@ -185,23 +155,15 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) {
|
||||
}
|
||||
|
||||
// Get all the descendants of the referenced node
|
||||
let descendents = LibraryNodes.find({
|
||||
'ancestors.id': referencedNode._id,
|
||||
let descendants = LibraryNodes.find({
|
||||
...getFilter.descendants(referencedNode),
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
|
||||
// We are adding the referenced node and its descendants
|
||||
let addedNodes = [referencedNode, ...descendents];
|
||||
|
||||
// re-map all the ancestors to parent the new sub-tree into our existing
|
||||
// node tree
|
||||
setLineageOfDocs({
|
||||
docArray: addedNodes,
|
||||
newAncestry: node.ancestors,
|
||||
oldParent: referencedNode.parent,
|
||||
});
|
||||
let addedNodes = [referencedNode, ...descendants];
|
||||
|
||||
// Filter all the looped references
|
||||
addedNodes = addedNodes.filter(addedNode => {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { setDocToLastOrder } from '/imports/api/parenting/order';
|
||||
|
||||
export function setDocToLastMixin(methodOptions) {
|
||||
// Make sure the doc has a charId
|
||||
// This mixin should come before simpleSchemaMixin so that it can extend the
|
||||
// schema before it is turned into a validate function
|
||||
if (methodOptions.validate) {
|
||||
throw new Meteor.Error(`setDocToLastMixin should come before simpleSchemaMixin`);
|
||||
}
|
||||
methodOptions.schema = new SimpleSchema({
|
||||
charId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).extend(methodOptions.schema);
|
||||
let collection = methodOptions.collection;
|
||||
if (!collection) {
|
||||
throw new Meteor.Error("`collection` required in method options for setDocToLastMixin");
|
||||
}
|
||||
let runFunc = methodOptions.run;
|
||||
methodOptions.run = function (doc) {
|
||||
setDocToLastOrder({ collection, doc });
|
||||
return runFunc.apply(this, arguments);
|
||||
};
|
||||
return methodOptions;
|
||||
}
|
||||
@@ -63,10 +63,14 @@ const ChildSchema = new SimpleSchema({
|
||||
left: {
|
||||
type: Number,
|
||||
index: 1,
|
||||
// Default to absolutely last with space for right
|
||||
defaultValue: Number.MAX_SAFE_INTEGER - 1,
|
||||
},
|
||||
right: {
|
||||
type: Number,
|
||||
index: 1,
|
||||
// Default to zero children, so right = left + 1
|
||||
defaultValue: Number.MAX_SAFE_INTEGER,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -11,13 +11,23 @@ export function getCollectionByName(name: string): Mongo.Collection<TreeDoc> {
|
||||
return collection;
|
||||
}
|
||||
|
||||
export function fetchDocByRef(ref: Reference, options?: Mongo.Options<object>): Promise<TreeDoc> {
|
||||
const doc = getCollectionByName(ref.collection).findOneAsync(ref.id, options);
|
||||
function assertDocFound(doc) {
|
||||
if (!doc) {
|
||||
throw new Meteor.Error('document-not-found',
|
||||
`No document could be found with id: ${ref.id} in ${ref.collection}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchDocByRefAsync(ref: Reference, options?: Mongo.Options<object>): Promise<TreeDoc> {
|
||||
const doc = getCollectionByName(ref.collection).findOneAsync(ref.id, options);
|
||||
assertDocFound(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function fetchDocByRef(ref: Reference, options?: Mongo.Options<object>): TreeDoc {
|
||||
const doc: TreeDoc = getCollectionByName(ref.collection).findOne(ref.id, options);
|
||||
assertDocFound(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -274,15 +284,11 @@ export const getFilter = {
|
||||
},
|
||||
}
|
||||
|
||||
export function fetchParent({ id, collection }) {
|
||||
return fetchDocByRef({ id, collection });
|
||||
}
|
||||
|
||||
/**
|
||||
* Give documents new random ids and transform their references.
|
||||
* Transform collections of re-IDed docs according to the collection map
|
||||
*/
|
||||
export function renewDocIds({ docArray, collectionMap, idMap = {} }) {
|
||||
export function renewDocIds({ docArray, collectionMap = {}, idMap = {} }) {
|
||||
// idMap is a map of {oldId: newId}
|
||||
// Get a random generator that's consistent on client and server
|
||||
const randomSrc = DDP.randomStream('renewDocIds');
|
||||
@@ -295,16 +301,21 @@ export function renewDocIds({ docArray, collectionMap, idMap = {} }) {
|
||||
idMap[oldId] = newId;
|
||||
});
|
||||
|
||||
// Remap all references using the new IDs
|
||||
const remapReference = ref => {
|
||||
if (idMap[ref.id]) {
|
||||
ref.id = idMap[ref.id];
|
||||
ref.collection = collectionMap && collectionMap[ref.collection] || ref.collection;
|
||||
}
|
||||
}
|
||||
// Get the id from the map if it exists, leave unchanged otherwise
|
||||
const remap = id => idMap[id] || id
|
||||
|
||||
// If there are references by id that need to be maintained when copying from
|
||||
// a library, here is where we would update them
|
||||
docArray.forEach(doc => {
|
||||
remapReference(doc.parent);
|
||||
remapReference(doc.root);
|
||||
// Remap the root and parent ids
|
||||
doc.root.id = remap(doc.root.id);
|
||||
doc.root.collection = collectionMap[doc.root.collection] || doc.root.collection;
|
||||
doc.parentId = remap(doc.parentId);
|
||||
|
||||
// Remap itemIds of items selected as ammo
|
||||
doc.resource?.itemsConsumed?.forEach(itemConsumed => {
|
||||
itemConsumed.itemId = remap(itemConsumed.itemId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user