mirror of
https://github.com/vale981/grapher
synced 2025-03-04 17:11:38 -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);
|
||||
|
||||
return recursiveCompose(rootNode);
|
||||
return recursiveCompose(rootNode, undefined, {scoped: isScoped});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -18,6 +18,5 @@ export const ExposeSchema = {
|
|||
),
|
||||
validateParams: Match.Maybe(
|
||||
Match.OneOf(Object, Function)
|
||||
),
|
||||
scoped: Match.Maybe(Boolean),
|
||||
)
|
||||
};
|
||||
|
|
|
@ -184,8 +184,7 @@ export default class extends Base {
|
|||
createGraph(this.collection, body),
|
||||
undefined, {
|
||||
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 postListResolver from './postListResolver';
|
||||
import postListResolverCached from './postListResolverCached';
|
||||
import userListScoped from './userListScoped';
|
||||
|
||||
export {
|
||||
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 postListExposureScoped from './bootstrap/queries/postListExposureScoped';
|
||||
import userListScoped from './bootstrap/queries/userListScoped';
|
||||
import { createQuery } from 'meteor/cultofcoders:grapher';
|
||||
import Posts from '../../query/testing/bootstrap/posts/collection';
|
||||
import Users from '../../query/testing/bootstrap/users/collection';
|
||||
|
||||
describe('Named Query', function() {
|
||||
it('Should return proper values', function(done) {
|
||||
|
@ -130,11 +132,13 @@ describe('Named Query', function() {
|
|||
|
||||
const docMap = Posts._collection._docs._map;
|
||||
const scopeField = `_sub_${handle.subscriptionId}`;
|
||||
const queryPathField = '_query_path_posts';
|
||||
data.forEach(post => {
|
||||
// no scope field returned from find
|
||||
assert.isUndefined(post[scopeField]);
|
||||
assert.isObject(docMap[post._id]);
|
||||
assert.equal(docMap[post._id][scopeField], 1);
|
||||
assert.equal(docMap[post._id][queryPathField], 1);
|
||||
});
|
||||
|
||||
done();
|
||||
|
@ -142,6 +146,46 @@ describe('Named Query', function() {
|
|||
});
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Should work with reactive queries via import', function(done) {
|
||||
const query = postListExposure.clone({
|
||||
title: 'User Post - 3',
|
||||
|
|
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 {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) {
|
||||
const originalObserve = cursor.observe;
|
||||
cursor.observe = function (callbacks) {
|
||||
const newCallbacks = Object.assign({}, callbacks);
|
||||
if (callbacks.added) {
|
||||
newCallbacks.added = doc => {
|
||||
doc[`__query_path_${ns}`] = 1;
|
||||
doc = _.clone(doc);
|
||||
doc[`_query_path_${ns}`] = 1;
|
||||
callbacks.added(doc);
|
||||
};
|
||||
}
|
||||
|
@ -15,7 +22,7 @@ function patchCursor(cursor, ns) {
|
|||
};
|
||||
}
|
||||
|
||||
function compose(node, userId) {
|
||||
function compose(node, userId, config) {
|
||||
return {
|
||||
find(parent) {
|
||||
if (parent) {
|
||||
|
@ -34,7 +41,9 @@ function compose(node, userId) {
|
|||
}
|
||||
|
||||
const cursor = accessor.find(filters, options, userId);
|
||||
patchCursor(cursor, getNodeNamespace(node));
|
||||
if (config.scoped) {
|
||||
patchCursor(cursor, getNodeNamespace(node));
|
||||
}
|
||||
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 {
|
||||
find() {
|
||||
let {filters, options} = applyProps(node);
|
||||
|
||||
const cursor = node.collection.find(filters, options, userId);
|
||||
patchCursor(cursor, getNodeNamespace(node));
|
||||
if (config.scoped) {
|
||||
patchCursor(cursor, getNodeNamespace(node));
|
||||
}
|
||||
return cursor;
|
||||
},
|
||||
|
||||
children: _.map(node.collectionNodes, n => {
|
||||
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) {
|
||||
_.extend(filters, fetchOptions.subscriptionHandle.scopeQuery());
|
||||
}
|
||||
// does not make sense without scoped parameter
|
||||
if (fetchOptions.scoped && fetchOptions.queryScoped) {
|
||||
_.extend(filters, {[`__query_path_${getNodeNamespace(node)}`]: {$exists: true}});
|
||||
// add query path filter
|
||||
if (fetchOptions.scoped) {
|
||||
_.extend(filters, {[`_query_path_${getNodeNamespace(node)}`]: {$exists: true}});
|
||||
}
|
||||
|
||||
let results = [];
|
||||
|
|
|
@ -7,16 +7,19 @@ import Comments from './comments/collection';
|
|||
import Posts from './posts/collection';
|
||||
import Tags from './tags/collection';
|
||||
import Groups from './groups/collection';
|
||||
import Users from './users/collection';
|
||||
|
||||
Authors.remove({});
|
||||
Comments.remove({});
|
||||
Posts.remove({});
|
||||
Tags.remove({});
|
||||
Groups.remove({});
|
||||
Users.remove({});
|
||||
|
||||
const AUTHORS = 6;
|
||||
const POST_PER_USER = 6;
|
||||
const COMMENTS_PER_POST = 6;
|
||||
const USERS = 4;
|
||||
const TAGS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
||||
const GROUPS = ['JavaScript', 'Meteor', 'React', 'Other'];
|
||||
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.');
|
||||
|
|
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 './groups/links';
|
||||
import './security/links';
|
||||
import './users/links';
|
||||
|
||||
import Posts from './posts/collection';
|
||||
import Groups from './groups/collection';
|
||||
import Authors from './authors/collection';
|
||||
import Users from './users/collection';
|
||||
|
||||
if (Meteor.isServer) {
|
||||
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