I am building a Chat app in Meteor 1.3, ES6 and React.
I have 3 collections:
1. People: Who has an array of conversations that the person are involved.
2. Conversations: That also have the people that are involved.
3. Messages: That has a conversation_id field to relate to second collection.
So I am using reywood:publish-composite package, to manage the reactive joins:
=> Get Conversations for this People (user)
=> Get the Messages of all the Conversations of this user
=> Filter messages according to the conversation selected (a react state)
I am also using the package React-Komposer from Kadira.
This is my code:
// publications.js
Meteor.publishComposite('compositeChat', function (people_id) {
return {
find() {
return Collections.People.find({_id: people_id});
},
children: [
{
find(people) {
return Collections.Conversations.find(
{_id: {$in: people.conversations }},
{sort: {'lastMessage.date': -1}}
);
}
},
{
find(people) {
return Collections.Messages.find(
{conversation_id: {$in: people.conversations }},
{sort: {date: 1}}
);
}
},
{
find(people) {
return Collections.People.find(
{_id: {$in: people.contacts }}
);
}
}
]
};
});
// AppContainer.js
import {composeAll, composeWithTracker} from 'react-komposer';
import AppWrapper from '../AppWrapper.jsx';
import * as Collections from '../../../../lib/collections/collections.js';
function composeChat(props, onData) {
let user;
if (!Meteor.userId()) {
user = 'qXSWv64qKaEPAu5yp';
} else {
user = Meteor.userId();
//console.log('Meteor.userId()', Meteor.userId());
}
const
chatSub = Meteor.subscribe('compositeChat', user);
if (chatSub.ready()) {
const chatData = {
chatLoading:
!chatSub.ready(),
myUserData:
Collections.People.findOne({_id: user}),
myContacts:
Collections.People.find(
{_id: { $ne: user}}, { sort: { name: 1 } }
).fetch(),
myConversations:
Collections.Conversations.find(
{}, { sort: { date: -1 } }
).fetch(),
noConversationContacts:
(function () {
return myFunctions.chat.noConversationContacts(
Collections.People.find(
{_id: { $ne: user}}
).fetch(),
Collections.Conversations.find().fetch()
);
}()),
myMessages:
Collections.Messages.find(
{}, {sort: {'lastMessage.date': -1}}
).fetch(),
myOtherPeople:
(function () {
return myFunctions.chat.getTheOtherPeople(
Collections.Conversations.find().fetch(),
user
);
}()),
unreaded:
(function () {
return myFunctions.chat.countUnreadedMessages(
user,
Collections.Conversations.find().fetch(),
Collections.Messages.find().fetch()
);
}())
};
console.log('chatData', chatData);
onData(null, {chatData});
}
}
export default composeWithTracker(composeChat)(AppWrapper);
The problem is: When new message is added, I am not getting the update in the UI, but the data is there (in Mongo), so if I refresh the page, I get the new messages...
In my previous version of the app (Meteor 1.2) this reactive joins worked perfectly.
What could be wrong?
Does publish-composite package not work with Meteor 1.3?
Does publish-composite can't be used with React-Komposer?
Do I have to mix them in other way?
Is there another option (with or without this packages) to manage reactive joins in Meteor 1.3?
Thanks
Related
I'm using GraphQL with Meteor and Pup v2, and I have a problem accessing the users data via a special ID provided to every user on signup, this ID will be used in a link (mysite.com/user/specialId) so other users can view the searched users account. Problem is, I can't get the data with the special ID, I can't get any data back if I don't pass in the users _id provided by MongoDB. Below I have a bunch of the code used:
Attempt 1
I tried to use a custom on-the-go way just to be able to at least access the data to see if it works (and then implement it correctly later)
const GET_USER_DETAILS = gql`
query user($userId: String) {
user(userId: $userId) {
userId
username
_id
}
}
`;
Here I export so I can get the data:
export default compose(
graphql(GET_USER_DETAILS, {
options: ({ match }) => ({
fetchPolicy: 'no-cache',
variables: {
// existing specialId for testing purposes, to be replaced with match.params.userId
userId: "J4xZzvvhBDSEufnBn",
},
}),
}),
)(PublicProfileView);
This returns a 400 error Network error: Response not successful: Received status code 400 error and after multiple attempts, I could not fix it, so I tried a different approach...
Attempt 2
I tried to go deep into the files and change the GraphQL. Created a new query:
query userById($userId: String) {
userById(userId: $userId) {
...UserAttributes
}
}
(Mentioned fragment)
fragment UserAttributes on User {
_id
name {
...
}
username
emailAddress
oAuthProvider
roles {
...
}
settings {
...
}
userId
}
Tried to add new item in API:
type Query {
...
userById(userId: String): User
...
}
Resolver:
resolvers: {
Query: {
...
userById: (parent, args) => {
// Assuming args equals an object like { _id: '123' };
return UserQueries.userById(args);
},
},
},
query.js, attempt 1:
userById: (parent) => queryUsers.find({ userId: parent.userId }, { sort: { createdAt: 1 } }).fetch()
Attempt 2:
userById: (parent, args, context) => {
return queryUsers({
userId: parent.userId,
});
},
And finally
Attempt 3
I tried to modify the get query
const getQueryModified = (options) => {
// console.log(options.userId)
try {
return options.userId
? { 'userId': options.userId }
: { userId: options.userId };
} catch (exception) {
throw new Error(`[queryUsers.getQuery] ${exception.message}`);
}
};
Here is the original query I tried to modify:
const getQuery = (options) => {
try {
return options.search
? {
_id: { $ne: options.currentUser._id },
$or: [
{ 'profile.name.first': options.search },
{ 'profile.name.last': options.search },
{ 'emails.address': options.search },
// { 'userId': options.search },
{ 'services.facebook.first_name': options.search },
{ 'services.facebook.last_name': options.search },
{ 'services.facebook.email': options.search },
],
}
: { _id: options.currentUser._id };
} catch (exception) {
throw new Error(`[queryUsers.getQuery] ${exception.message}`);
}
};
Unfortunately this was also unsuccessful, the best I get from these when executing the below query is null...
userById(userId: "J4xZzvvhBDSEufnBn"){
username
}
All I want is to get the user data from their userId and not their _id, but I can't seem to figure out how to do it
This is how my flowrouter looks like,
I tried all three options shown below: but unable to subscribe
import {CompanySettings} from '../imports/api/companysettingsMaster.js';
// And imported the api also..
FlowRouter.route('/', {
name: 'home',
subscriptions: function() {
// 1.
return this.register('companySettings', Meteor.subscribe('companySettings'));
// 2.
this.register('CompanySettings', Meteor.subscribe('companySettings'));
// 3.
return Meteor.subscribe('companySettings');
},
action: function() {
var themeSettings = CompanySettings.findOne({
"companyId": 101
});
if (themeSettings) {
console.log(themeSettings);
var scaleProcess = themeSettings.generalSettings.scaleProcess;
if (scaleProcess == 'retail')
BlazeLayout.render("retailMainLayout", {
content: "homepages"
});
else {
BlazeLayout.render("WSEmainLayout", {
content: "homepages"
});
}
} else {
console.log('no themeSettings');
}
}
});
But, not getting document at the end .. Any suggestions.. Thanks in advance
I got the answer for subscription in flowrouter which is as follows:
FlowRouter.route('/', {
waitOn: function () {
return Meteor.subscribe('companySettings');
},
});
Here companySettings is a name of collection in mongodb
I have the following route :
this.route('groupPage', {
path: '/group/:_groupId',
waitOn: function(){
return Meteor.subscribe("groupPage", this.params._groupId);
},
data: function() {
var group = Groups.findOne({_id: this.params._groupId});
var members = Meteor.users.find({_id : {$in: group.memberIds}}); ******** ISSUE HERE******
return {
group: group,
members: members,
}; }});
and the following publication :
Meteor.publishComposite('groupPage', function(groupId, sortOrder, limit) {
return {
// return the group
find: function() {
if(this.userId){
var selector = {_id: groupId};
var options = {limit: 1};
return Groups.find(selector, options);
}
else{
return ;
}
},
children: [
{ // return the members
find: function(group) {
var selector = {_id: {$in: group.memberIds} };
return Meteor.users.find(selector);
}
}
]}}) ;
Now my issue is that : when the related page renders for the first there is no problems but when i actualize the group Page view the line : var members = Meteor.users.find({_id : {$in: group.memberIds}}); gives me the error : undefined object don't have memberIds property. i guess it's because the subscription is not yet ready when doing group.memberIds , isn't it ? Please a hint.
Thanks.
The data function doesn't wait for the subscription to be ready. Further more, subscriptions in the router are considered an anti-pattern for the most part, and should be done in the template: https://www.discovermeteor.com/blog/template-level-subscriptions/
I would pass to the template the groupId, and then get the group and members in the template, like so:
this.route('groupPage', {
path: '/group/:_groupId',
data: function() {
return {
_groupId: this.params._groupId,
}
}
});
and then in the template file:
Template.groupPage.onCreated(function(){
this.subscribe("groupPage", this.data._groupId);
})
Template.groupPage.helpers({
members(function(){
tempInst = Template.instance()
var group = Groups.findOne({_id: tempInst.data._groupId});
return Meteor.users.find({_id : {$in: group.memberIds}});
})
})
The general pattern of your route and publication are all solid. I suspect it's something simple such as:
There is no group with the _id you're using
You're not logged in when you load the route
Here's a version of your code that guards against the error. Note that the publication executes this.ready() instead of just returning if the user is not logged in.
this.route('groupPage', {
path: '/group/:_groupId',
waitOn: function(){
return Meteor.subscribe("groupPage", this.params._groupId);
},
data: function() {
var group = Groups.findOne({_id: this.params._groupId});
var members = group && Meteor.users.find({_id : {$in: group.memberIds}});
return { group: group, members: members };
}
});
Meteor.publishComposite('groupPage', function(groupId,sortOrder,limit) {
return {
find: function() {
if (this.userId) return Groups.find(groupId);
this.ready()
}
},
children: [
find: function(group) {
var selector = {_id: {$in: group.memberIds} };
return Meteor.users.find(selector);
}
]
});
I'm using meteor tabular and publish composite addons.
I want to pub/sub user data based on user role, user data is getting send to client however not being displayed by tabular addon (for admin role only, it display fine for super-admin role, see code below).
My publish code:
Meteor.publishComposite('tabular_users', function (tableName, ids, fields) {
this.unblock();
return {
find: function () {
if (Roles.userIsInRole(this.userId, ['super-admin'], 'admin')) {
return Meteor.users.find({_id: {$in: ids}}, {fields: fields});
} else if (Roles.userIsInRole(this.userId, ['admin'], 'property-managers')) {
return Meteor.users.find({ "$and" : [
{ _id: { $in: ids } },
{ "profile.property_manager_id": Meteor.users.findOne(this.userId).profile.property_manager_id }
]}, {fields: fields});
} else {
this.stop();
return;
}
},
children: [
{
find: function(user) {
return PropertyManagers.find(
{ _id: user.profile.property_manager_id }
);
}
}
]
};
});
I've been modifying the example meteor app at http://meteor.com/examples/leaderboard. As you can see in the code bellow, I'm trying to update the score of players upon someone hitting the reset button. This updated fine on the client side but in my console I noticed the error "update failed: 500 -- Internal server error". Upon further inspection I saw that indeed, the server side database was not being updated. Any thoughts? (relevant code is in the reset function but I've posted the rest here just in case)
// Set up a collection to contain player information. On the server,
// it is backed by a MongoDB collection named "players."
Players = new Meteor.Collection("players");
var SORT_OPTIONS = {
name: {name: 1, score: -1},
score: {score: -1, name: 1}
}
var NAMES = [ "Ada Lovelace",
"Grace Hopper",
"Marie Curie",
"Carl Friedrich Gauss",
"Nikola Tesla",
"Claude Shannon" ];
function reset(options) {
if (options && options['seed'] === true) {
for (var i = 0; i < NAMES.length; i++) {
Players.insert({ name: NAMES[i], score: Math.floor(Math.random()*10)*5 });
}
}
if (options && options['restart'] === true) {
Players.update( {},
{ $set: { score: Math.floor(Math.random()*10)*5 } },
{multi: true});
}
}
if (Meteor.is_client) {
Template.leaderboard.players = function () {
var sort_by = SORT_OPTIONS[Session.get("sort_by")]
return Players.find({}, {sort: sort_by});
};
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Template.player.selected = function () {
return Session.equals("selected_player", this._id) ? "selected" : '';
};
Template.leaderboard.events = {
'click input.inc': function () {
Players.update(Session.get("selected_player"), {$inc: {score: 5}});
},
'click input.sort': function () {
Session.get("sort_by") == "score" ? Session.set("sort_by", "name") : Session.set("sort_by", "score");
},
'click input.reset': function () {
reset({'restart': true});
}
};
Template.player.events = {
'click': function () {
Session.set("selected_player", this._id);
}
};
}
// On server startup, create some players if the database is empty.
if (Meteor.is_server) {
Meteor.startup(function () {
if (Players.find().count() === 0) {
reset({'seed': true});
}
});
}
This also happened to me, but checking the server log, the problem I had was that the $inc modifier requires a number for the argument for the update method, so I made sure it got it with
Number()
Time went by and it now works :) I guess it was some server issue on their demo deploy site.