mirror of
https://github.com/vale981/grapher
synced 2025-03-05 09:31:42 -05:00
Updates to scoped query handling
This commit is contained in:
parent
a04edaf59f
commit
70fd03a743
13 changed files with 121 additions and 14 deletions
|
@ -174,7 +174,7 @@ _.extend(NamedQuery.prototype, {
|
||||||
|
|
||||||
const rootNode = createGraph(self.collection, body);
|
const rootNode = createGraph(self.collection, body);
|
||||||
|
|
||||||
return recursiveCompose(rootNode);
|
return recursiveCompose(rootNode, undefined, {scoped: isScoped});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,5 @@ export const ExposeSchema = {
|
||||||
),
|
),
|
||||||
validateParams: Match.Maybe(
|
validateParams: Match.Maybe(
|
||||||
Match.OneOf(Object, Function)
|
Match.OneOf(Object, Function)
|
||||||
),
|
)
|
||||||
scoped: Match.Maybe(Boolean),
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -184,8 +184,7 @@ export default class extends Base {
|
||||||
createGraph(this.collection, body),
|
createGraph(this.collection, body),
|
||||||
undefined, {
|
undefined, {
|
||||||
scoped: this.options.scoped,
|
scoped: this.options.scoped,
|
||||||
queryScoped: true,
|
subscriptionHandle: this.subscriptionHandle
|
||||||
subscriptionHandle: this.subscriptionHandle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import postListParamsCheck from './postListParamsCheck';
|
||||||
import postListParamsCheckServer from './postListParamsCheckServer';
|
import postListParamsCheckServer from './postListParamsCheckServer';
|
||||||
import postListResolver from './postListResolver';
|
import postListResolver from './postListResolver';
|
||||||
import postListResolverCached from './postListResolverCached';
|
import postListResolverCached from './postListResolverCached';
|
||||||
|
import userListScoped from './userListScoped';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
postList,
|
postList,
|
||||||
|
|
26
lib/namedQuery/testing/bootstrap/queries/userListScoped.js
Executable file
26
lib/namedQuery/testing/bootstrap/queries/userListScoped.js
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||||
|
|
||||||
|
const userListScoped = createQuery('userListScoped', {
|
||||||
|
users: {
|
||||||
|
name: 1,
|
||||||
|
friends: {
|
||||||
|
name: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scoped: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
userListScoped.expose({
|
||||||
|
firewall() {
|
||||||
|
},
|
||||||
|
embody: {
|
||||||
|
$filter({filters, params}) {
|
||||||
|
filters.name = params.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default userListScoped;
|
|
@ -1,7 +1,9 @@
|
||||||
import postListExposure from './bootstrap/queries/postListExposure.js';
|
import postListExposure from './bootstrap/queries/postListExposure.js';
|
||||||
import postListExposureScoped from './bootstrap/queries/postListExposureScoped';
|
import postListExposureScoped from './bootstrap/queries/postListExposureScoped';
|
||||||
|
import userListScoped from './bootstrap/queries/userListScoped';
|
||||||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||||
import Posts from '../../query/testing/bootstrap/posts/collection';
|
import Posts from '../../query/testing/bootstrap/posts/collection';
|
||||||
|
import Users from '../../query/testing/bootstrap/users/collection';
|
||||||
|
|
||||||
describe('Named Query', function() {
|
describe('Named Query', function() {
|
||||||
it('Should return proper values', function(done) {
|
it('Should return proper values', function(done) {
|
||||||
|
@ -130,11 +132,53 @@ describe('Named Query', function() {
|
||||||
|
|
||||||
const docMap = Posts._collection._docs._map;
|
const docMap = Posts._collection._docs._map;
|
||||||
const scopeField = `_sub_${handle.subscriptionId}`;
|
const scopeField = `_sub_${handle.subscriptionId}`;
|
||||||
|
const queryPathField = '_query_path_posts';
|
||||||
data.forEach(post => {
|
data.forEach(post => {
|
||||||
// no scope field returned from find
|
// no scope field returned from find
|
||||||
assert.isUndefined(post[scopeField]);
|
assert.isUndefined(post[scopeField]);
|
||||||
assert.isObject(docMap[post._id]);
|
assert.isObject(docMap[post._id]);
|
||||||
assert.equal(docMap[post._id][scopeField], 1);
|
assert.equal(docMap[post._id][scopeField], 1);
|
||||||
|
assert.equal(docMap[post._id][queryPathField], 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should work with reactive recursive scoped queries', function (done) {
|
||||||
|
const query = userListScoped.clone({name: 'User - 3'});
|
||||||
|
|
||||||
|
const handle = query.subscribe();
|
||||||
|
Tracker.autorun(c => {
|
||||||
|
if (handle.ready()) {
|
||||||
|
c.stop();
|
||||||
|
const data = query.fetch();
|
||||||
|
handle.stop();
|
||||||
|
|
||||||
|
assert.equal(data.length, 1);
|
||||||
|
// User 3 has users 0,1,2 as friends
|
||||||
|
const [user3] = data;
|
||||||
|
assert.equal(user3.friends.length, 3);
|
||||||
|
|
||||||
|
const docMap = Users._collection._docs._map;
|
||||||
|
// users collection on the client should have 4 items (user 3 and friends - user 0,1,2)
|
||||||
|
assert.equal(_.keys(docMap).length, 4);
|
||||||
|
|
||||||
|
const scopeField = `_sub_${handle.subscriptionId}`;
|
||||||
|
const rootQueryPathField = '_query_path_users';
|
||||||
|
const nestedQueryPathField = '_query_path_users_users';
|
||||||
|
Object.entries(docMap).forEach(([userId, userDoc]) => {
|
||||||
|
const isRoot = userId === user3._id;
|
||||||
|
assert.equal(userDoc[scopeField], 1);
|
||||||
|
if (isRoot) {
|
||||||
|
assert.equal(userDoc[rootQueryPathField], 1);
|
||||||
|
assert.isTrue(!(nestedQueryPathField in userDoc));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert.equal(userDoc[nestedQueryPathField], 1);
|
||||||
|
assert.isTrue(!(rootQueryPathField in userDoc));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|
0
lib/namedQuery/testing/server.test.js
Normal file → Executable file
0
lib/namedQuery/testing/server.test.js
Normal file → Executable file
|
@ -1,13 +1,20 @@
|
||||||
import applyProps from './applyProps.js';
|
import applyProps from './applyProps.js';
|
||||||
import {getNodeNamespace} from './createGraph';
|
import {getNodeNamespace} from './createGraph';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds _query_path fields to the cursor docs which are used for scoped query filtering on the client.
|
||||||
|
*
|
||||||
|
* @param cursor
|
||||||
|
* @param ns
|
||||||
|
*/
|
||||||
function patchCursor(cursor, ns) {
|
function patchCursor(cursor, ns) {
|
||||||
const originalObserve = cursor.observe;
|
const originalObserve = cursor.observe;
|
||||||
cursor.observe = function (callbacks) {
|
cursor.observe = function (callbacks) {
|
||||||
const newCallbacks = Object.assign({}, callbacks);
|
const newCallbacks = Object.assign({}, callbacks);
|
||||||
if (callbacks.added) {
|
if (callbacks.added) {
|
||||||
newCallbacks.added = doc => {
|
newCallbacks.added = doc => {
|
||||||
doc[`__query_path_${ns}`] = 1;
|
doc = _.clone(doc);
|
||||||
|
doc[`_query_path_${ns}`] = 1;
|
||||||
callbacks.added(doc);
|
callbacks.added(doc);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,7 +22,7 @@ function patchCursor(cursor, ns) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function compose(node, userId) {
|
function compose(node, userId, config) {
|
||||||
return {
|
return {
|
||||||
find(parent) {
|
find(parent) {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
@ -34,7 +41,9 @@ function compose(node, userId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursor = accessor.find(filters, options, userId);
|
const cursor = accessor.find(filters, options, userId);
|
||||||
|
if (config.scoped) {
|
||||||
patchCursor(cursor, getNodeNamespace(node));
|
patchCursor(cursor, getNodeNamespace(node));
|
||||||
|
}
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -43,20 +52,22 @@ function compose(node, userId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (node, userId, config = {bypassFirewalls: false}) => {
|
export default (node, userId, config = {bypassFirewalls: false, scoped: false}) => {
|
||||||
return {
|
return {
|
||||||
find() {
|
find() {
|
||||||
let {filters, options} = applyProps(node);
|
let {filters, options} = applyProps(node);
|
||||||
|
|
||||||
const cursor = node.collection.find(filters, options, userId);
|
const cursor = node.collection.find(filters, options, userId);
|
||||||
|
if (config.scoped) {
|
||||||
patchCursor(cursor, getNodeNamespace(node));
|
patchCursor(cursor, getNodeNamespace(node));
|
||||||
|
}
|
||||||
return cursor;
|
return cursor;
|
||||||
},
|
},
|
||||||
|
|
||||||
children: _.map(node.collectionNodes, n => {
|
children: _.map(node.collectionNodes, n => {
|
||||||
const userIdToPass = (config.bypassFirewalls) ? undefined : userId;
|
const userIdToPass = (config.bypassFirewalls) ? undefined : userId;
|
||||||
|
|
||||||
return compose(n, userIdToPass);
|
return compose(n, userIdToPass, config);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,9 +17,9 @@ function fetch(node, parentObject, fetchOptions = {}) {
|
||||||
if (fetchOptions.scoped && fetchOptions.subscriptionHandle) {
|
if (fetchOptions.scoped && fetchOptions.subscriptionHandle) {
|
||||||
_.extend(filters, fetchOptions.subscriptionHandle.scopeQuery());
|
_.extend(filters, fetchOptions.subscriptionHandle.scopeQuery());
|
||||||
}
|
}
|
||||||
// does not make sense without scoped parameter
|
// add query path filter
|
||||||
if (fetchOptions.scoped && fetchOptions.queryScoped) {
|
if (fetchOptions.scoped) {
|
||||||
_.extend(filters, {[`__query_path_${getNodeNamespace(node)}`]: {$exists: true}});
|
_.extend(filters, {[`_query_path_${getNodeNamespace(node)}`]: {$exists: true}});
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = [];
|
let results = [];
|
||||||
|
|
|
@ -7,16 +7,19 @@ import Comments from './comments/collection';
|
||||||
import Posts from './posts/collection';
|
import Posts from './posts/collection';
|
||||||
import Tags from './tags/collection';
|
import Tags from './tags/collection';
|
||||||
import Groups from './groups/collection';
|
import Groups from './groups/collection';
|
||||||
|
import Users from './users/collection';
|
||||||
|
|
||||||
Authors.remove({});
|
Authors.remove({});
|
||||||
Comments.remove({});
|
Comments.remove({});
|
||||||
Posts.remove({});
|
Posts.remove({});
|
||||||
Tags.remove({});
|
Tags.remove({});
|
||||||
Groups.remove({});
|
Groups.remove({});
|
||||||
|
Users.remove({});
|
||||||
|
|
||||||
const AUTHORS = 6;
|
const AUTHORS = 6;
|
||||||
const POST_PER_USER = 6;
|
const POST_PER_USER = 6;
|
||||||
const COMMENTS_PER_POST = 6;
|
const COMMENTS_PER_POST = 6;
|
||||||
|
const USERS = 4;
|
||||||
const TAGS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
const TAGS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
||||||
const GROUPS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
const GROUPS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
||||||
const COMMENT_TEXT_SAMPLES = [
|
const COMMENT_TEXT_SAMPLES = [
|
||||||
|
@ -81,4 +84,15 @@ _.each(authors, (author) => {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const friendIds = [];
|
||||||
|
// each user is created so his friends are previously added users
|
||||||
|
_.range(USERS).forEach(idx => {
|
||||||
|
const id = Users.insert({
|
||||||
|
name: `User - ${idx}`,
|
||||||
|
friendIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
friendIds.push(id);
|
||||||
|
});
|
||||||
|
|
||||||
console.log('[ok] fixtures have been loaded.');
|
console.log('[ok] fixtures have been loaded.');
|
||||||
|
|
2
lib/query/testing/bootstrap/index.js
Normal file → Executable file
2
lib/query/testing/bootstrap/index.js
Normal file → Executable file
|
@ -4,10 +4,12 @@ import './authors/links';
|
||||||
import './tags/links';
|
import './tags/links';
|
||||||
import './groups/links';
|
import './groups/links';
|
||||||
import './security/links';
|
import './security/links';
|
||||||
|
import './users/links';
|
||||||
|
|
||||||
import Posts from './posts/collection';
|
import Posts from './posts/collection';
|
||||||
import Groups from './groups/collection';
|
import Groups from './groups/collection';
|
||||||
import Authors from './authors/collection';
|
import Authors from './authors/collection';
|
||||||
|
import Users from './users/collection';
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Posts.expose();
|
Posts.expose();
|
||||||
|
|
2
lib/query/testing/bootstrap/users/collection.js
Executable file
2
lib/query/testing/bootstrap/users/collection.js
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
const Users = new Mongo.Collection('users');
|
||||||
|
export default Users;
|
9
lib/query/testing/bootstrap/users/links.js
Executable file
9
lib/query/testing/bootstrap/users/links.js
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
import Users from './collection';
|
||||||
|
|
||||||
|
Users.addLinks({
|
||||||
|
friends: {
|
||||||
|
collection: Users,
|
||||||
|
field: 'friendIds',
|
||||||
|
type: 'many'
|
||||||
|
},
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue