Some changes to how parenting and ordering of docs interface
This commit is contained in:
@@ -8,37 +8,10 @@ import getModifierFields from '/imports/api/getModifierFields.js';
|
|||||||
|
|
||||||
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
||||||
|
|
||||||
const RefSchema = new SimpleSchema({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
|
||||||
index: 1
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let LibraryNodeSchema = schema({
|
let LibraryNodeSchema = schema({
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
libraryNodeType: {
|
libraryNodeType: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: Object.keys(librarySchemas),
|
allowedValues: Object.keys(librarySchemas),
|
||||||
},
|
|
||||||
order: {
|
|
||||||
type: SimpleSchema.Integer,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
parent: {
|
|
||||||
type: RefSchema,
|
|
||||||
},
|
|
||||||
// ancestors[0] should be the library to check for permission
|
|
||||||
ancestors: {
|
|
||||||
type: Array,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
'ancestors.$': {
|
|
||||||
type: RefSchema,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import schema from '/imports/api/schema.js';
|
import schema from '/imports/api/schema.js';
|
||||||
|
|
||||||
const inhertitedFieldsSchema = new SimpleSchema({
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
enabled: {
|
|
||||||
type: Boolean,
|
|
||||||
optional: true,
|
|
||||||
index: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const RefSchema = new SimpleSchema({
|
const RefSchema = new SimpleSchema({
|
||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -24,9 +12,11 @@ const RefSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
RefSchema.extend(inhertitedFieldsSchema);
|
|
||||||
|
|
||||||
let ChildSchema = schema({
|
let ChildSchema = schema({
|
||||||
|
order: {
|
||||||
|
type: Number,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
parent: {
|
parent: {
|
||||||
type: RefSchema,
|
type: RefSchema,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -40,7 +30,4 @@ let ChildSchema = schema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const inheritedFields = new Set(inhertitedFieldsSchema.objectKeys());
|
|
||||||
|
|
||||||
export default ChildSchema;
|
export default ChildSchema;
|
||||||
export { inheritedFields };
|
|
||||||
|
|||||||
@@ -1,8 +1,52 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
||||||
|
|
||||||
export function getHighestOrder({collection, rootAncestor}){
|
// Docs keep track of their order amongst their siblings and keep a copy of the
|
||||||
|
// order of their ancestors. Order is first compared between oldest non-shared
|
||||||
|
// ancestors, then by ancestors before children, then between order of siblings.
|
||||||
|
export function compareOrder(docA, docB){
|
||||||
|
// < 0 if A comes before B
|
||||||
|
// = 0 if A and B are the same order
|
||||||
|
// > 0 if B comes before A
|
||||||
|
|
||||||
|
// Documents are equal order to themselves
|
||||||
|
if (docA._id && docB._id && docA._id === docB._id){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they are siblings, just compare order
|
||||||
|
if (docA.parent.id === docB.parent.id){
|
||||||
|
return docA.order - docB.order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// They must share a root ancestor to be meaningfully sorted
|
||||||
|
if (docA.ancestors[0].id !== docB.ancestors[0].id){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through their ancestors after the root, and find the first order
|
||||||
|
// difference
|
||||||
|
let i, difference;
|
||||||
|
const length = Math.min(docA.ancestors.length, docB.ancestors.length);
|
||||||
|
for (i = 1; i < length; i++){
|
||||||
|
difference = docA.ancestors[i].order - docB.ancestors[i].order;
|
||||||
|
if (difference){
|
||||||
|
return difference;
|
||||||
|
} else if (docA.ancestors[i].id !== docB.ancestors[i].id) {
|
||||||
|
throw new Meteor.Error('Sibling order clash',
|
||||||
|
'Sibling docs share the same order, sort failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We haven't returned yet, all ancestors up to this point are shared and one
|
||||||
|
// doc has no more ancestors implying one is an ancestor of the other,
|
||||||
|
// return the difference in their ancestor list lengths, shorter comes first
|
||||||
|
return docA.ancestors.length - docB.ancestors.length
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHighestOrder({collection, parentId}){
|
||||||
const highestOrderedDoc = collection.findOne({
|
const highestOrderedDoc = collection.findOne({
|
||||||
'ancestors.0': rootAncestor,
|
'parent.id': parentId,
|
||||||
}, {
|
}, {
|
||||||
fields: {order: 1},
|
fields: {order: 1},
|
||||||
sort: {order: -1},
|
sort: {order: -1},
|
||||||
@@ -13,17 +57,22 @@ export function getHighestOrder({collection, rootAncestor}){
|
|||||||
export function setDocToLastOrder({collection, doc}){
|
export function setDocToLastOrder({collection, doc}){
|
||||||
doc.order = getHighestOrder({
|
doc.order = getHighestOrder({
|
||||||
collection,
|
collection,
|
||||||
rootAncestor: doc.ancestors[0],
|
parentId: doc.parent.id,
|
||||||
}) + 1;
|
}) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDocOrder({collection, doc, order}){
|
export function updateOrder({docRef, order}){
|
||||||
|
let doc = fetchDocByRef(docRef, {fields: {
|
||||||
|
order: 1,
|
||||||
|
parent: 1,
|
||||||
|
}});
|
||||||
|
let collection = getCollectionByName(docRef.collection);
|
||||||
const currentOrder = doc.order;
|
const currentOrder = doc.order;
|
||||||
if (currentOrder === order){
|
if (currentOrder === order){
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Move the document to its new order
|
// Move the document to its new order
|
||||||
collection.update(doc._id, {$set: {order}});
|
docRef.collection.update(doc._id, {$set: {order}});
|
||||||
let inBetweenSelector, increment;
|
let inBetweenSelector, increment;
|
||||||
if (order > currentOrder){
|
if (order > currentOrder){
|
||||||
// Move in-between docs backward
|
// Move in-between docs backward
|
||||||
@@ -41,8 +90,8 @@ export function setDocOrder({collection, doc, order}){
|
|||||||
increment = 1;
|
increment = 1;
|
||||||
}
|
}
|
||||||
collection.update({
|
collection.update({
|
||||||
|
'parent.id': doc.parent.id,
|
||||||
order: {$and: inBetweenSelector},
|
order: {$and: inBetweenSelector},
|
||||||
rootAncestor: doc.ancestors[0],
|
|
||||||
}, {
|
}, {
|
||||||
$inc: {order: increment},
|
$inc: {order: increment},
|
||||||
}, {
|
}, {
|
||||||
@@ -51,10 +100,10 @@ export function setDocOrder({collection, doc, order}){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reorderDocs({collection, rootAncestor}){
|
export function reorderDocs({collection, parentId}){
|
||||||
let bulkWrite = [];
|
let bulkWrite = [];
|
||||||
collection.find({
|
collection.find({
|
||||||
'ancestors.0': rootAncestor,
|
'parent.id': parentId,
|
||||||
}, {
|
}, {
|
||||||
fields: {order: 1},
|
fields: {order: 1},
|
||||||
sort: {order: 1}
|
sort: {order: 1}
|
||||||
|
|||||||
6
app/imports/api/parenting/organizeDoc.js
Normal file
6
app/imports/api/parenting/organizeDoc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { updateParent } from '/imports/api/parenting/parenting.js';
|
||||||
|
|
||||||
|
export default function organizeDoc({docRef, parentRef, order}){
|
||||||
|
updateParent({docRef, parentRef});
|
||||||
|
updateOrder({docRef, order})
|
||||||
|
};
|
||||||
@@ -2,83 +2,57 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
|||||||
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
||||||
import SimpleSchema from 'simpl-schema';
|
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}){
|
export function fetchParent({id, collection}){
|
||||||
return fetchDocByRef({id, collection});
|
return fetchDocByRef({id, collection});
|
||||||
}
|
}
|
||||||
|
|
||||||
// n database hits to get the children by parent id
|
export function fetchChildren({ collection, parentId, filter = {}, options = {sort: {order: 1}} }){
|
||||||
export function fetchChildren({parentId, filter = {}, options}){
|
|
||||||
filter["parent.id"] = parentId;
|
filter["parent.id"] = parentId;
|
||||||
let children = [];
|
let children = [];
|
||||||
collections.forEach(collection => {
|
children.push(
|
||||||
children.push(
|
...collection.find({
|
||||||
...collection.find({
|
"parent.id": parentId
|
||||||
"parent.id": parentId
|
}, options).fetch()
|
||||||
}, options).fetch()
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
// n database hits to update the decendents
|
export function updateChildren({collection, parentId, filter = {}, modifier, options={}}){
|
||||||
export function updateChildren({parentId, filter = {}, modifier, options={}}){
|
|
||||||
filter["parent.id"] = parentId;
|
filter["parent.id"] = parentId;
|
||||||
options.multi = true;
|
options.multi = true;
|
||||||
collections.forEach(collection => {
|
collection.update(filter, modifier, options);
|
||||||
collection.update(filter, modifier, options);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// n database hits to fetch the decendents by ancestor id, in no particular order
|
export function fetchDecendents({ collection, ancestorId, filter = {}, options}){
|
||||||
export function fetchDecendents({ancestorId, filter = {}, options}){
|
|
||||||
filter["ancestors.id"] = ancestorId;
|
filter["ancestors.id"] = ancestorId;
|
||||||
let decendents = [];
|
let decendents = [];
|
||||||
collections.forEach(collection => {
|
decendents.push(...collection.find(filter, options).fetch());
|
||||||
decendents.push(...collection.find(filter, options).fetch());
|
|
||||||
});
|
|
||||||
return decendents;
|
return decendents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// n database hits to update the decendents
|
export function updateDecendents({collection, ancestorId, filter = {}, modifier, options={}}){
|
||||||
export function updateDecendents({ancestorId, filter = {}, modifier, options={}}){
|
|
||||||
filter["ancestors.id"] = ancestorId;
|
filter["ancestors.id"] = ancestorId;
|
||||||
options.multi = true;
|
options.multi = true;
|
||||||
collections.forEach(collection => {
|
collection.update(filter, modifier, options);
|
||||||
collection.update(filter, modifier, options);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// n database hits to get decendents to act on
|
export function forEachDecendent({collection, ancestorId, filter = {}, options}, callback){
|
||||||
export function forEachDecendent({ancestorId, filter = {}, options}, callback){
|
|
||||||
filter["ancestors.id"] = ancestorId;
|
filter["ancestors.id"] = ancestorId;
|
||||||
collections.forEach(collection => {
|
collection.find(filter, options).forEach(callback);
|
||||||
collection.find(filter, options).forEach(callback);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 database read
|
// 1 database read
|
||||||
// TODO generalise for all inheritedFields
|
export function getAncestry({parentRef, inheritedFields = {}}){
|
||||||
export function getAncestry({id, collection}){
|
// Ancestry must include ancestors
|
||||||
// Get the parent ref
|
inheritedFields.ancestors = 1;
|
||||||
let parentDoc = fetchDocByRef({id, collection}, {fields: {
|
|
||||||
name: 1,
|
let parentDoc = fetchDocByRef(parentRef, {fields: inheritedFields});
|
||||||
enabled: 1,
|
let parent = { ...parentRef};
|
||||||
ancestors: 1,
|
for (let field in inheritedFields){
|
||||||
}});
|
if (inheritedFields[field]){
|
||||||
let parent = {
|
parent[field] = parentDoc[field];
|
||||||
id,
|
}
|
||||||
collection,
|
}
|
||||||
name: parentDoc.name,
|
|
||||||
enabled: parentDoc.enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ancestors is [...parent's ancestors, parent ref]
|
// Ancestors is [...parent's ancestors, parent ref]
|
||||||
let ancestors = parentDoc.ancestors || [];
|
let ancestors = parentDoc.ancestors || [];
|
||||||
@@ -87,77 +61,7 @@ export function getAncestry({id, collection}){
|
|||||||
return {parent, ancestors};
|
return {parent, ancestors};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDocAncestryMixin(methodOptions){
|
export function updateParent({docRef, parentRef}){
|
||||||
// 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 collection = getCollectionByName(docRef.collection);
|
||||||
let oldDoc = fetchDocByRef(docRef, {fields: {
|
let oldDoc = fetchDocByRef(docRef, {fields: {
|
||||||
parent: 1,
|
parent: 1,
|
||||||
@@ -168,7 +72,7 @@ export function updateParent(docRef, parentRef){
|
|||||||
if (oldDoc.parent.id === parentRef.id) return;
|
if (oldDoc.parent.id === parentRef.id) return;
|
||||||
|
|
||||||
// update the document's parenting
|
// update the document's parenting
|
||||||
let {parent, ancestors} = getAncestry(parentRef);
|
let {parent, ancestors} = getAncestry({parentRef});
|
||||||
collection.update(docRef.id, {$set: {parent, ancestors}});
|
collection.update(docRef.id, {$set: {parent, ancestors}});
|
||||||
|
|
||||||
// Remove the old ancestors from the decendents
|
// Remove the old ancestors from the decendents
|
||||||
@@ -191,9 +95,7 @@ export function updateParent(docRef, parentRef){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO these rely on hard coding inherited fields
|
// TODO move these functions to character properties collection
|
||||||
// the inherited fields should only appear on the childChema, nowhere else
|
|
||||||
// Move these somewhere appropriate
|
|
||||||
export function findEnabled(collection, query, options){
|
export function findEnabled(collection, query, options){
|
||||||
query.enabled = true;
|
query.enabled = true;
|
||||||
query['ancestors.$.enabled'] = {$not: false};
|
query['ancestors.$.enabled'] = {$not: false};
|
||||||
|
|||||||
Reference in New Issue
Block a user