mirror of
https://github.com/vale981/grapher
synced 2025-03-06 01:51:38 -05:00
Merge pull request #44 from cult-of-coders/feature/41-exposure-body
[RFC] Added exposure to body #41
This commit is contained in:
commit
ce3fe7e3c0
20 changed files with 381 additions and 53 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
## 1.1.12
|
||||||
|
- Added body to exposure that will intersect with the actual request
|
||||||
|
|
||||||
## 1.1.11
|
## 1.1.11
|
||||||
- Written rigurous unit tests for deep cloning
|
- Written rigurous unit tests for deep cloning
|
||||||
- Auto-adding $metadata field when coming from an inversed link.
|
- Auto-adding $metadata field when coming from an inversed link.
|
||||||
|
|
|
@ -41,7 +41,12 @@ function extractCollectionDocumentation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentationObject[name] = {};
|
DocumentationObject[name] = {};
|
||||||
DocumentationObject[name]['isExposed'] = !!instance.__isExposedForGrapher;
|
var isExposed = !!instance.__isExposedForGrapher;
|
||||||
|
DocumentationObject[name]['isExposed'] = isExposed;
|
||||||
|
|
||||||
|
if (isExposed && instance.__exposure.config.body) {
|
||||||
|
DocumentationObject[name]['exposureBody'] = deepClone(instance.__exposure.config.body);
|
||||||
|
}
|
||||||
|
|
||||||
extractSchema(DocumentationObject[name], instance);
|
extractSchema(DocumentationObject[name], instance);
|
||||||
extractLinks(DocumentationObject[name], instance);
|
extractLinks(DocumentationObject[name], instance);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
||||||
|
import createGraph from '../query/lib/createGraph.js';
|
||||||
|
|
||||||
export default new SimpleSchema({
|
let Schema = new SimpleSchema({
|
||||||
firewall: {
|
firewall: {
|
||||||
type: Function,
|
type: Function,
|
||||||
optional: true
|
optional: true
|
||||||
|
@ -36,5 +37,23 @@ export default new SimpleSchema({
|
||||||
method: {
|
method: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
|
},
|
||||||
|
|
||||||
|
body: {
|
||||||
|
type: null,
|
||||||
|
blackbox: true,
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Schema;
|
||||||
|
|
||||||
|
_.extend(Schema, {
|
||||||
|
validateBody(collection, body) {
|
||||||
|
try {
|
||||||
|
createGraph(collection, body);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Meteor.Error('invalid-body', 'We could not build a valid graph when trying to create your exposure: ' + e.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -4,6 +4,8 @@ import hypernova from '../query/hypernova/hypernova.js';
|
||||||
import ExposureConfigSchema from './exposure.config.schema.js';
|
import ExposureConfigSchema from './exposure.config.schema.js';
|
||||||
import enforceMaxDepth from './lib/enforceMaxDepth.js';
|
import enforceMaxDepth from './lib/enforceMaxDepth.js';
|
||||||
import enforceMaxLimit from './lib/enforceMaxLimit.js';
|
import enforceMaxLimit from './lib/enforceMaxLimit.js';
|
||||||
|
import intersectDeep from './lib/intersectDeep.js';
|
||||||
|
import deepClone from '../query/lib/deepClone';
|
||||||
import restrictFieldsFn from './lib/restrictFields.js';
|
import restrictFieldsFn from './lib/restrictFields.js';
|
||||||
import restrictLinks from './lib/restrictLinks.js';
|
import restrictLinks from './lib/restrictLinks.js';
|
||||||
|
|
||||||
|
@ -61,44 +63,87 @@ export default class Exposure {
|
||||||
ExposureConfigSchema.clean(this.config);
|
ExposureConfigSchema.clean(this.config);
|
||||||
ExposureConfigSchema.validate(this.config);
|
ExposureConfigSchema.validate(this.config);
|
||||||
|
|
||||||
|
if (this.config.body) {
|
||||||
|
ExposureConfigSchema.validateBody(this.collection, this.config.body);
|
||||||
|
}
|
||||||
|
|
||||||
this.config = _.extend({}, Exposure.getConfig(), this.config);
|
this.config = _.extend({}, Exposure.getConfig(), this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the body and intersects it with the exposure body, if it exists.
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @param userId
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getTransformedBody(body, userId) {
|
||||||
|
if (!this.config.body) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersectDeep(this.getBody(userId), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the exposure body
|
||||||
|
*/
|
||||||
|
getBody(userId) {
|
||||||
|
if (!this.config.body) {
|
||||||
|
throw new Meteor.Error('Cannot get exposure body because it was not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isFunction(this.config.body)) {
|
||||||
|
return deepClone(
|
||||||
|
this.config.body.call(this, userId)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return deepClone(this.config.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initPublication() {
|
initPublication() {
|
||||||
const collection = this.collection;
|
const collection = this.collection;
|
||||||
const config = this.config;
|
const config = this.config;
|
||||||
|
const getTransformedBody = this.getTransformedBody.bind(this);
|
||||||
|
|
||||||
Meteor.publishComposite(this.name, function (body) {
|
Meteor.publishComposite(this.name, function (body) {
|
||||||
const rootNode = createGraph(collection, body);
|
let transformedBody = getTransformedBody(body);
|
||||||
|
const rootNode = createGraph(collection, transformedBody);
|
||||||
|
|
||||||
enforceMaxDepth(rootNode, config.maxDepth);
|
enforceMaxDepth(rootNode, config.maxDepth);
|
||||||
restrictLinks(rootNode, this.userId);
|
restrictLinks(rootNode, this.userId);
|
||||||
|
|
||||||
return recursiveCompose(rootNode, this.userId);
|
return recursiveCompose(rootNode, this.userId, {
|
||||||
|
bypassFirewalls: !!config.body
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initMethod() {
|
initMethod() {
|
||||||
const collection = this.collection;
|
const collection = this.collection;
|
||||||
const config = this.config;
|
const config = this.config;
|
||||||
|
const getTransformedBody = this.getTransformedBody.bind(this);
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
[this.name](body) {
|
[this.name](body) {
|
||||||
this.unblock();
|
this.unblock();
|
||||||
|
let transformedBody = getTransformedBody(body);
|
||||||
|
|
||||||
const rootNode = createGraph(collection, body);
|
const rootNode = createGraph(collection, transformedBody);
|
||||||
enforceMaxDepth(rootNode, config.maxDepth);
|
enforceMaxDepth(rootNode, config.maxDepth);
|
||||||
|
|
||||||
restrictLinks(rootNode, this.userId);
|
restrictLinks(rootNode, this.userId);
|
||||||
|
|
||||||
return hypernova(rootNode, this.userId);
|
return hypernova(rootNode, this.userId, {
|
||||||
|
bypassFirewalls: !!config.body
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initCountMethod() {
|
initCountMethod() {
|
||||||
const collection = this.collection;
|
const collection = this.collection;
|
||||||
const config = this.config;
|
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
[this.name + '.count'](body) {
|
[this.name + '.count'](body) {
|
||||||
|
|
32
lib/exposure/lib/intersectDeep.js
Normal file
32
lib/exposure/lib/intersectDeep.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import deepClone from '../../query/lib/deepClone';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given to objects, it will intersect what they have in common.
|
||||||
|
*
|
||||||
|
* It will favor objects on intersection meaning: { item: 1 } INTERSECT { item: { anything } } => { item: { anything } }
|
||||||
|
*
|
||||||
|
* @param first Object
|
||||||
|
* @param second Object
|
||||||
|
*/
|
||||||
|
export default function intersectDeep(first, second) {
|
||||||
|
let object = {};
|
||||||
|
_.each(first, (value, key) => {
|
||||||
|
if (second[key] !== undefined) {
|
||||||
|
if (_.isObject(value)) {
|
||||||
|
if (_.isObject(second[key])) {
|
||||||
|
object[key] = intersectDeep(value, second[key]);
|
||||||
|
} else {
|
||||||
|
object[key] = deepClone(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_.isObject(second[key])) {
|
||||||
|
object[key] = deepClone(second[key]);
|
||||||
|
} else {
|
||||||
|
object[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
|
@ -24,4 +24,3 @@ DemoLink.addLinks({
|
||||||
|
|
||||||
export const DemoPublication = new Mongo.Collection('DemoPublication');
|
export const DemoPublication = new Mongo.Collection('DemoPublication');
|
||||||
export const DemoMethod = new Mongo.Collection('DemoPublicationMethod');
|
export const DemoMethod = new Mongo.Collection('DemoPublicationMethod');
|
||||||
|
|
||||||
|
|
38
lib/exposure/testing/bootstrap/expose.js
Normal file
38
lib/exposure/testing/bootstrap/expose.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import Demo, {DemoPublication, DemoMethod} from './demo.js';
|
||||||
|
import Intersect, { CollectionLink as IntersectLink } from './intersect';
|
||||||
|
import { Exposure } from 'meteor/cultofcoders:grapher';
|
||||||
|
|
||||||
|
Demo.expose({
|
||||||
|
firewall(filters, options, userId) {
|
||||||
|
Exposure.restrictFields(filters, options, ['restrictedField']);
|
||||||
|
filters.isPrivate = false;
|
||||||
|
},
|
||||||
|
maxLimit: 2,
|
||||||
|
maxDepth: 2,
|
||||||
|
restrictLinks(userId) {
|
||||||
|
return ['restrictedLink'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DemoMethod.expose({
|
||||||
|
publication: false
|
||||||
|
});
|
||||||
|
|
||||||
|
DemoPublication.expose({
|
||||||
|
method: false
|
||||||
|
});
|
||||||
|
|
||||||
|
Intersect.expose({
|
||||||
|
body: {
|
||||||
|
value: 1,
|
||||||
|
link: {
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IntersectLink.expose({
|
||||||
|
firewall() {
|
||||||
|
throw new Meteor.Error('I do not allow!')
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,23 +5,17 @@ Exposure.setConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
import Demo, {DemoPublication, DemoMethod, DemoRestrictedLink} from './demo.js';
|
import Demo, {DemoPublication, DemoMethod, DemoRestrictedLink} from './demo.js';
|
||||||
|
import Intersect, { CollectionLink as IntersectLink } from './intersect';
|
||||||
|
|
||||||
Demo.remove({});
|
Demo.remove({});
|
||||||
|
DemoRestrictedLink.remove({});
|
||||||
|
|
||||||
Demo.insert({
|
Intersect.remove({});
|
||||||
isPrivate: true,
|
IntersectLink.remove({});
|
||||||
restrictedField: 'PRIVATE'
|
|
||||||
});
|
|
||||||
|
|
||||||
Demo.insert({
|
Demo.insert({isPrivate: true, restrictedField: 'PRIVATE'});
|
||||||
isPrivate: false,
|
Demo.insert({isPrivate: false, restrictedField: 'PRIVATE'});
|
||||||
restrictedField: 'PRIVATE'
|
Demo.insert({isPrivate: false, restrictedField: 'PRIVATE'});
|
||||||
});
|
|
||||||
|
|
||||||
Demo.insert({
|
|
||||||
isPrivate: false,
|
|
||||||
restrictedField: 'PRIVATE'
|
|
||||||
});
|
|
||||||
|
|
||||||
const restrictedDemoId = Demo.insert({
|
const restrictedDemoId = Demo.insert({
|
||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
|
@ -32,20 +26,18 @@ Demo.getLink(restrictedDemoId, 'restrictedLink').set({
|
||||||
test: true
|
test: true
|
||||||
});
|
});
|
||||||
|
|
||||||
Demo.expose({
|
// INTERSECTION TEST LINKS
|
||||||
firewall(filters, options, userId) {
|
|
||||||
Exposure.restrictFields(filters, options, ['restrictedField']);
|
const intersectId = Intersect.insert({
|
||||||
filters.isPrivate = false;
|
value: 'Hello',
|
||||||
},
|
privateValue: 'Bad!'
|
||||||
maxLimit: 2,
|
|
||||||
maxDepth: 2,
|
|
||||||
restrictLinks(userId) {
|
|
||||||
return ['restrictedLink'];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
DemoMethod.expose({
|
|
||||||
publication: false
|
const intersectLinkId = IntersectLink.insert({
|
||||||
});
|
value: 'Hello, I am a Link',
|
||||||
DemoPublication.expose({
|
privateValue: 'Bad!'
|
||||||
method: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Intersect.getLink(intersectId, 'link').set(intersectLinkId);
|
||||||
|
Intersect.getLink(intersectId, 'privateLink').set(intersectLinkId);
|
||||||
|
IntersectLink.getLink(intersectLinkId, 'myself').set(intersectLinkId);
|
||||||
|
|
22
lib/exposure/testing/bootstrap/intersect.js
Normal file
22
lib/exposure/testing/bootstrap/intersect.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const Collection = new Mongo.Collection('exposure_intersect');
|
||||||
|
export default Collection;
|
||||||
|
|
||||||
|
export const CollectionLink = new Mongo.Collection('exposure_intersect_link');
|
||||||
|
|
||||||
|
Collection.addLinks({
|
||||||
|
link: {
|
||||||
|
collection: CollectionLink,
|
||||||
|
type: 'one'
|
||||||
|
},
|
||||||
|
privateLink: {
|
||||||
|
collection: CollectionLink,
|
||||||
|
type: 'one'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CollectionLink.addLinks({
|
||||||
|
myself: {
|
||||||
|
type: 'one',
|
||||||
|
collection: CollectionLink
|
||||||
|
}
|
||||||
|
});
|
|
@ -3,7 +3,9 @@ import Demo, {
|
||||||
DemoPublication
|
DemoPublication
|
||||||
} from './bootstrap/demo.js';
|
} from './bootstrap/demo.js';
|
||||||
|
|
||||||
describe('Exposure', function () {
|
import Intersect, { CollectionLink as IntersectLink } from './bootstrap/intersect';
|
||||||
|
|
||||||
|
describe('Exposure Tests', function () {
|
||||||
it('Should fetch only allowed data and limitations should be applied', function (done) {
|
it('Should fetch only allowed data and limitations should be applied', function (done) {
|
||||||
const query = Demo.createQuery({
|
const query = Demo.createQuery({
|
||||||
$options: {limit: 3},
|
$options: {limit: 3},
|
||||||
|
@ -98,7 +100,7 @@ describe('Exposure', function () {
|
||||||
it('Should restrict links # restrictLinks ', function (done) {
|
it('Should restrict links # restrictLinks ', function (done) {
|
||||||
const query = Demo.createQuery({
|
const query = Demo.createQuery({
|
||||||
_id: 1,
|
_id: 1,
|
||||||
restrictedLink: 1
|
restrictedLink: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
query.fetch((err, res) => {
|
query.fetch((err, res) => {
|
||||||
|
@ -114,4 +116,82 @@ describe('Exposure', function () {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should intersect the body graphs - Method', function (done) {
|
||||||
|
const query = Intersect.createQuery({
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1,
|
||||||
|
link: {
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1,
|
||||||
|
myself: {
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
privateLink: {
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
query.fetch((err, res) => {
|
||||||
|
assert.isUndefined(err);
|
||||||
|
assert.lengthOf(res, 1);
|
||||||
|
|
||||||
|
const result = _.first(res);
|
||||||
|
|
||||||
|
assert.isDefined(result.value);
|
||||||
|
assert.isUndefined(result.privateValue);
|
||||||
|
assert.isUndefined(result.privateLink);
|
||||||
|
|
||||||
|
assert.isObject(result.link);
|
||||||
|
assert.isDefined(result.link.value);
|
||||||
|
assert.isUndefined(result.link.privateValue);
|
||||||
|
assert.isUndefined(result.link.myself);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should intersect the body graphs - Subscription', function (done) {
|
||||||
|
const query = Intersect.createQuery({
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1,
|
||||||
|
link: {
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1,
|
||||||
|
myself: {
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
privateLink: {
|
||||||
|
value: 1,
|
||||||
|
privateValue: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handle = query.subscribe();
|
||||||
|
|
||||||
|
Tracker.autorun((c) => {
|
||||||
|
if (handle.ready()) {
|
||||||
|
c.stop();
|
||||||
|
const res = query.fetch();
|
||||||
|
|
||||||
|
assert.lengthOf(res, 1);
|
||||||
|
|
||||||
|
const result = _.first(res);
|
||||||
|
|
||||||
|
assert.isDefined(result.value);
|
||||||
|
assert.isUndefined(result.privateValue);
|
||||||
|
assert.isUndefined(result.privateLink);
|
||||||
|
|
||||||
|
assert.isObject(result.link);
|
||||||
|
assert.isDefined(result.link.value);
|
||||||
|
assert.isUndefined(result.link.privateValue);
|
||||||
|
assert.isUndefined(result.link.myself);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
import './bootstrap/fixtures.js';
|
import './bootstrap/fixtures.js';
|
||||||
|
import './bootstrap/expose.js';
|
||||||
|
|
||||||
import './units/units.js';
|
import './units/units.js';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import restrictFields from '../../lib/restrictFields.js';
|
import restrictFields from '../../lib/restrictFields.js';
|
||||||
import enforceMaxLimit from '../../lib/enforceMaxLimit.js';
|
import enforceMaxLimit from '../../lib/enforceMaxLimit.js';
|
||||||
|
import intersectDeep from '../../lib/intersectDeep.js';
|
||||||
import enforceMaxDepth, {getDepth} from '../../lib/enforceMaxDepth.js';
|
import enforceMaxDepth, {getDepth} from '../../lib/enforceMaxDepth.js';
|
||||||
import CollectionNode from '../../../query/nodes/collectionNode.js';
|
import CollectionNode from '../../../query/nodes/collectionNode.js';
|
||||||
|
|
||||||
|
@ -154,4 +155,63 @@ describe('Unit Tests', function () {
|
||||||
|
|
||||||
assert.throws(fn, /graph request is too deep/);
|
assert.throws(fn, /graph request is too deep/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should intersect two objects deeply', function () {
|
||||||
|
const obj1 = {
|
||||||
|
a: 1,
|
||||||
|
b: 1,
|
||||||
|
c: {
|
||||||
|
c1: 1,
|
||||||
|
c2: 1
|
||||||
|
},
|
||||||
|
d: {
|
||||||
|
d1: {
|
||||||
|
d11: 1,
|
||||||
|
d12: 1,
|
||||||
|
d13: {
|
||||||
|
d131: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const obj2 = {
|
||||||
|
a: 1,
|
||||||
|
x: '!',
|
||||||
|
b: {
|
||||||
|
b1: 1
|
||||||
|
},
|
||||||
|
c: {
|
||||||
|
c1: 1,
|
||||||
|
c3: '!'
|
||||||
|
},
|
||||||
|
d: {
|
||||||
|
d2: '!',
|
||||||
|
d1: {
|
||||||
|
d11: 1,
|
||||||
|
d13: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.freeze(obj1);
|
||||||
|
Object.freeze(obj2);
|
||||||
|
|
||||||
|
const result = intersectDeep(obj1, obj2);
|
||||||
|
|
||||||
|
assert.isObject(result);
|
||||||
|
assert.equal(result.a, 1);
|
||||||
|
assert.isObject(result.b);
|
||||||
|
assert.equal(result.b.b1, 1);
|
||||||
|
assert.isUndefined(result.x);
|
||||||
|
assert.equal(result.c.c1, 1);
|
||||||
|
assert.isUndefined(result.c.c2);
|
||||||
|
assert.isUndefined(result.c.c3);
|
||||||
|
assert.equal(result.d.d1.d11, 1);
|
||||||
|
assert.isUndefined(result.d.d1.d12);
|
||||||
|
assert.isUndefined(result.d.d2);
|
||||||
|
|
||||||
|
assert.isObject(result.d.d1.d13);
|
||||||
|
assert.equal(result.d.d1.d13.d131, 1);
|
||||||
|
})
|
||||||
});
|
});
|
|
@ -4,6 +4,7 @@ import mergeDeep from './lib/mergeDeep.js';
|
||||||
import createGraph from '../../query/lib/createGraph.js';
|
import createGraph from '../../query/lib/createGraph.js';
|
||||||
import recursiveCompose from '../../query/lib/recursiveCompose.js';
|
import recursiveCompose from '../../query/lib/recursiveCompose.js';
|
||||||
import applyFilterFunction from '../../query/lib/applyFilterFunction.js';
|
import applyFilterFunction from '../../query/lib/applyFilterFunction.js';
|
||||||
|
import deepClone from '../../query/lib/deepClone.js';
|
||||||
|
|
||||||
_.extend(NamedQuery.prototype, {
|
_.extend(NamedQuery.prototype, {
|
||||||
expose(config = {}) {
|
expose(config = {}) {
|
||||||
|
@ -33,7 +34,10 @@ _.extend(NamedQuery.prototype, {
|
||||||
this._initCountMethod();
|
this._initCountMethod();
|
||||||
|
|
||||||
if (config.embody) {
|
if (config.embody) {
|
||||||
this.body = mergeDeep(this.body, config.embody);
|
this.body = mergeDeep(
|
||||||
|
deepClone(this.body),
|
||||||
|
config.embody
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExposed = true;
|
this.isExposed = true;
|
||||||
|
|
|
@ -3,7 +3,10 @@ import deepClone from '../query/lib/deepClone.js';
|
||||||
export default class {
|
export default class {
|
||||||
constructor(name, collection, body, params = {}) {
|
constructor(name, collection, body, params = {}) {
|
||||||
this.queryName = name;
|
this.queryName = name;
|
||||||
this.body = body;
|
|
||||||
|
this.body = deepClone(body);
|
||||||
|
Object.freeze(this.body);
|
||||||
|
|
||||||
this.subscriptionHandle = null;
|
this.subscriptionHandle = null;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import LinkResolve from '../../links/linkTypes/linkResolve.js';
|
||||||
import storeHypernovaResults from './storeHypernovaResults.js';
|
import storeHypernovaResults from './storeHypernovaResults.js';
|
||||||
import assembler from './assembler.js';
|
import assembler from './assembler.js';
|
||||||
|
|
||||||
function hypernova(collectionNode, userId, debug) {
|
function hypernova(collectionNode, userId) {
|
||||||
_.each(collectionNode.collectionNodes, childCollectionNode => {
|
_.each(collectionNode.collectionNodes, childCollectionNode => {
|
||||||
let {filters, options} = applyProps(childCollectionNode);
|
let {filters, options} = applyProps(childCollectionNode);
|
||||||
|
|
||||||
|
@ -13,22 +13,22 @@ function hypernova(collectionNode, userId, debug) {
|
||||||
result[childCollectionNode.linkName] = accessor.find(filters, options);
|
result[childCollectionNode.linkName] = accessor.find(filters, options);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
storeHypernovaResults(childCollectionNode, userId, debug);
|
storeHypernovaResults(childCollectionNode, userId);
|
||||||
|
|
||||||
hypernova(childCollectionNode, userId, debug);
|
hypernova(childCollectionNode, userId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function hypernovaInit(collectionNode, userId, debug) {
|
export default function hypernovaInit(collectionNode, userId, config = {bypassFirewalls: false}) {
|
||||||
let {filters, options} = applyProps(collectionNode);
|
let {filters, options} = applyProps(collectionNode);
|
||||||
|
|
||||||
const collection = collectionNode.collection;
|
const collection = collectionNode.collection;
|
||||||
|
|
||||||
collectionNode.results = collection.find(filters, options, userId).fetch();
|
collectionNode.results = collection.find(filters, options, userId).fetch();
|
||||||
|
|
||||||
hypernova(collectionNode, userId, debug);
|
const userIdToPass = (config.bypassFirewalls) ? undefined : userId;
|
||||||
|
hypernova(collectionNode, userIdToPass);
|
||||||
|
|
||||||
return collectionNode.results;
|
return collectionNode.results;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import assemble from './assembler.js';
|
||||||
import assembleAggregateResults from './assembleAggregateResults.js';
|
import assembleAggregateResults from './assembleAggregateResults.js';
|
||||||
import buildAggregatePipeline from './buildAggregatePipeline.js';
|
import buildAggregatePipeline from './buildAggregatePipeline.js';
|
||||||
|
|
||||||
export default function storeHypernovaResults(childCollectionNode, userId, debug) {
|
export default function storeHypernovaResults(childCollectionNode, userId) {
|
||||||
if (childCollectionNode.parent.results.length === 0) {
|
if (childCollectionNode.parent.results.length === 0) {
|
||||||
return childCollectionNode.results = [];
|
return childCollectionNode.results = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import applyProps from './applyProps.js';
|
import applyProps from './applyProps.js';
|
||||||
|
|
||||||
export default function compose(node, userId) {
|
function compose(node, userId) {
|
||||||
return {
|
return {
|
||||||
find(parent) {
|
find(parent) {
|
||||||
let {filters, options} = applyProps(node);
|
let {filters, options} = applyProps(node);
|
||||||
|
@ -28,3 +28,19 @@ export default function compose(node, userId) {
|
||||||
children: _.map(node.collectionNodes, n => compose(n, userId))
|
children: _.map(node.collectionNodes, n => compose(n, userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default (node, userId, config = {bypassFirewalls: false}) => {
|
||||||
|
return {
|
||||||
|
find() {
|
||||||
|
let {filters, options} = applyProps(node);
|
||||||
|
|
||||||
|
return node.collection.find(filters, options, userId);
|
||||||
|
},
|
||||||
|
|
||||||
|
children: _.map(node.collectionNodes, n => {
|
||||||
|
const userIdToPass = (config.bypassFirewalls) ? undefined : userId;
|
||||||
|
|
||||||
|
return compose(n, userIdToPass);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,13 @@
|
||||||
import FieldNode from './fieldNode.js';
|
import FieldNode from './fieldNode.js';
|
||||||
|
import deepClone from '../lib/deepClone';
|
||||||
|
|
||||||
export default class CollectionNode {
|
export default class CollectionNode {
|
||||||
constructor(collection, body, linkName) {
|
constructor(collection, body = {}, linkName = null) {
|
||||||
this.body = body;
|
if (collection && !_.isObject(body)) {
|
||||||
|
throw new Meteor.Error('invalid-body', 'Every collection link should have its body defined as an object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.body = deepClone(body);
|
||||||
this.linkName = linkName;
|
this.linkName = linkName;
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,10 @@ import deepClone from './lib/deepClone.js';
|
||||||
export default class {
|
export default class {
|
||||||
constructor(collection, body, params = {}) {
|
constructor(collection, body, params = {}) {
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
this.body = body;
|
|
||||||
|
this.body = deepClone(body);
|
||||||
|
Object.freeze(this.body);
|
||||||
|
|
||||||
this._params = params;
|
this._params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'cultofcoders:grapher',
|
name: 'cultofcoders:grapher',
|
||||||
version: '1.1.11',
|
version: '1.1.12',
|
||||||
// Brief, one-line summary of the package.
|
// Brief, one-line summary of the package.
|
||||||
summary: 'Grapher makes linking collections easily. And fetching data as a graph.',
|
summary: 'Grapher makes linking collections easily. And fetching data as a graph.',
|
||||||
// URL to the Git repository containing the source code for this package.
|
// URL to the Git repository containing the source code for this package.
|
||||||
|
|
Loading…
Add table
Reference in a new issue