Files
DiceCloud/app/imports/api/parenting/parenting.js

210 lines
5.7 KiB
JavaScript

import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
import SimpleSchema from 'simpl-schema';
// n = collections.length
let collections = [];
export function registerCollection(collectionName){
collections.push(collectionName);
}
// 1 database hit to get the parent by reference
export function fetchParent({id, collection}){
return fetchDocByRef({id, collection});
}
// n database hits to get the children by parent id
export function fetchChildren({parentId, filter = {}, options}){
filter["parent.id"] = parentId;
let children = [];
collections.forEach(collection => {
children.push(
...collection.find({
"parent.id": parentId
}, options).fetch()
);
});
return children;
}
// n database hits to update the decendents
export function updateChildren({parentId, filter = {}, modifier, options={}}){
filter["parent.id"] = parentId;
options.multi = true;
collections.forEach(collection => {
collection.update(filter, modifier, options);
});
}
// n database hits to fetch the decendents by ancestor id, in no particular order
export function fetchDecendents({ancestorId, filter = {}, options}){
filter["ancestors.id"] = ancestorId;
let decendents = [];
collections.forEach(collection => {
decendents.push(...collection.find(filter, options).fetch());
});
return decendents;
}
// n database hits to update the decendents
export function updateDecendents({ancestorId, filter = {}, modifier, options={}}){
filter["ancestors.id"] = ancestorId;
options.multi = true;
collections.forEach(collection => {
collection.update(filter, modifier, options);
});
}
// n database hits to get decendents to act on
export function forEachDecendent({ancestorId, filter = {}, options}, callback){
filter["ancestors.id"] = ancestorId;
collections.forEach(collection => {
collection.find(filter, options).forEach(callback);
});
}
// 1 database read
// TODO generalise for all inheritedFields
export function getAncestry({id, collection}){
// Get the parent ref
let parentDoc = fetchDocByRef({id, collection}, {fields: {
name: 1,
enabled: 1,
ancestors: 1,
}});
let parent = {
id,
collection,
name: parentDoc.name,
enabled: parentDoc.enabled,
};
// Ancestors is [...parent's ancestors, parent ref]
let ancestors = parentDoc.ancestors || [];
ancestors.push(parent);
return {parent, ancestors};
}
export function setDocAncestryMixin(methodOptions){
// Extend the method's schema to require the needed properties
// This mixin should come before simpleschema mixin
methodOptions.schema = new SimpleSchema({
parent: {
type: Object,
optional: true,
},
'parent.id': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
'parent.collection': {
type: String,
},
}).extend(methodOptions.schema);
// Change the doc's ancestry before running
let runFunc = methodOptions.run;
methodOptions.run = function(doc, ...rest){
// If the doc's parent doesn't exist, set it to the character
if (!doc.parent && doc.charId) {
doc.parent = {id: doc.charId, collection: 'creatures'};
}
let {parent, ancestors} = getAncestry(doc.parent);
doc.parent = parent;
doc.ancestors = ancestors;
return runFunc.call(this, doc, ...rest);
};
return methodOptions;
}
function ensureAncestryContainsId(ancestors, id){
if (!id){
throw new Meteor.Error('ancestor-check-failed',
`Expected charId, got ${id}`
);
}
if (!ancestors){
throw new Meteor.Error('ancestor-check-failed',
`Expected ancestors array, got ${ancestors}`
);
}
for (let ancestor of ancestors){
if (ancestor.id === id){
return;
}
}
throw new Meteor.Error('ancestor-check-failed',
`Ancestors did not contain id: ${id}`
);
}
export function ensureAncestryContainsCharIdMixin(methodOptions){
// Extend the method's schema to require the needed properties
// This mixin should come before simpleSchemaMixin
methodOptions.schema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).extend(methodOptions.schema);
let runFunc = methodOptions.run;
methodOptions.run = function({charId, ancestors}){
ensureAncestryContainsId(ancestors, charId);
return runFunc.apply(this, arguments);
};
return methodOptions;
}
export function updateParent(docRef, parentRef){
let collection = getCollectionByName(docRef.collection);
let oldDoc = fetchDocByRef(docRef, {fields: {
parent: 1,
ancestors: 1,
}});
// Skip if we aren't changing the parent id
if (oldDoc.parent.id === parentRef.id) return;
// update the document's parenting
let {parent, ancestors} = getAncestry(parentRef);
collection.update(docRef.id, {$set: {parent, ancestors}});
// Remove the old ancestors from the decendents
updateDecendents({
ancestorId: docRef.id,
modifier: {$pullAll: {
ancestors: oldDoc.ancestors,
}},
});
// Add the new ancestors to the decendents
updateDecendents({
ancestorId: docRef.id,
modifier: {$push: {
ancestors: {
$each: ancestors,
$position: 0,
},
}},
});
}
// TODO these rely on hard coding inherited fields
// the inherited fields should only appear on the childChema, nowhere else
// Move these somewhere appropriate
export function findEnabled(collection, query, options){
query.enabled = true;
query['ancestors.$.enabled'] = {$not: false};
return collection.find(query, options);
}
export function getName(doc){
if (doc.name) return name;
var i = doc.ancestors.length;
while(i--) {
if (ancestors[i].name) return ancestors[i].name;
}
}