I want the Facebook accessToken that is stored in my user's document on the client. Following the meteor documentation, I should just add a new publish call.
In server.js:
Meteor.publish("access_token", function () {
return Meteor.users().find(
{ _id : Meteor.userId() },
{'services.facebook.accessToken': 1}
);
});
In client.js:
Meteor.subscribe("access_token");
Alright, here's where I get lost. Should the accessToken just show up in the Meteor.users collection now for the logged in user? Like:
var user = Meteor.users.findOne({ _id : Meteor.userId() });
console.log(user); // includes services.facebook.accessToken now
Obviously, I've tried the above and the accessToken doesn't show up. Yes, I've confirmed that the mongo document contains services.facebook.
So... do I create a new client collection and somehow hook it up to the new publish? How do I get the accessToken?
you should use "fields" keyword
Meteor.users.find({ _id: this.userId },
{ fields: { the-extra-fields-that-you-want-go-here: 1 } }
);
http://docs.meteor.com/#fieldspecifiers
You can publish the field you want:
Meteor.publish( null, function() {
Meteor.users.find({}, {fields: {profile: 1, username: 1, ...}})
}
Related
I am trying to create an Auth object in firebase that returns the User UID. I want to be able to create a document in my collection with that particuar UID but apparently geofirestore doesn't have a feature to add a document with a particular ID.
const storesCollection = geoFirestore.collection("retailers");
export const firstTimeStartCreateRetailer = ( email, password) => async dispatch => {
try {
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await storesCollection.doc(user.uid).add({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
})
dispatch({ type: LOGIN, payload: { ...user } });
} catch (error) {
console.log(error)
}
};
this code is rejected because geoFirestore doesn't have the .doc(id) referencing feature. How can I achieve this.
You need to do
await storesCollection.doc(user.uid).set({...})
using the set() method. As a matter of fact, there is no add() method for a GeoDocumentReference and storesCollection.doc(user.uid) is a GeoDocumentReference.
The add() method is a method of a GeoCollectionReference.
Because storesCollection is a GeoCollectionReference, the API is not always the same as native Firestore references.
In your particular case, you get the document you want to write to using doc(id), but instead of using add(...) which is used on collections, you need to use set(...) instead to create/overwrite the data for that particular document.
await storesCollection.doc(user.uid).set({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
});
I have a meteor app and I'm calling all the publish functions at once in the iron router configuration like below. I only want to return the subscriptions once the user is logged in, so I check Meteor.userId():
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: '404',
waitOn: function () {
if (Meteor.userId()) {
return [Meteor.subscribe('users'),
Meteor.subscribe('company'),
Meteor.subscribe('projects'),
Meteor.subscribe('columns'),
Meteor.subscribe('cards'),
Meteor.subscribe('contents'),
Meteor.subscribe('labels'),
Meteor.subscribe('notifications')];
}
}
});
The publish functions have all the same structure, dependent on user.companyId, like this:
Meteor.publish('cards', function () {
if (this.userId) {
const user = Meteor.users.findOne({ _id: this.userId, companyId: { $exists: true } });
if (user) {
return Cards.find({ companyId: user.companyId });
}
} else {
this.ready();
}
});
My problem is, when the user registers, the account is created and the companyId is saved to the user, but when they now login, the only way for the data to show up is to refresh the browser. I want it to be reactive.
From the meteor guide:
On the client, if anything in a reactive function changes, the whole
function will re-run, and the results are fairly intuitive.
On the server however, the reactivity is limited to the behavior of
the cursors you return from your publish functions. You’ll see any
changes to the data that matches their queries, but their queries
will never change.
You can indeed use reywood:publish-composite as suggested, but for your simple case I think reactive-publish would be much easier to get up and running.
Install the package, and just wrap your publication in a this.autorun:
Meteor.publish('cards', function () {
this.autorun( function() {
if (this.userId) {
const user = Meteor.users.findOne({ _id: this.userId, companyId: { $exists: true } });
if (user) {
return Cards.find({ companyId: user.companyId });
}
} else {
this.ready();
}
});
});
How can I add extra attributes phone number and address to this data set? It seems like Firebase documentation doesn't specify anything about that.
I have implemented the login, register and update using firebase.auth()
Login :
//Email Login
firebase.auth().signInWithEmailAndPassword(email, password).then(
ok => {
console.log("Logged in User",ok.user);
},
error => {
console.log("email/pass sign in error", error);
}
);
Register:
//Sign Up
firebase.auth().createUserWithEmailAndPassword(email, password).then(
ok => {
console.log("Register OK", ok);
},
error => {
console.log("Register error", error);
}
)
Update:
//User Authentication
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
$scope.data=user;
} else {
// No user, Redirect to login page
}
});
//Save Function
$scope.save=function(values){
$scope.data.updateProfile({
displayName: "Test User",
email: "test#gmail.com",
/* phone: 123412341,
address: "Temp Address",*/
photoURL: "www.example.com/profile/img.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
};
As far as I know, you have to manage the users profiles by yourself if you want to have more fields than the default user provided by Firebase.
You can do this creating a reference in Firebase to keep all the users profiles.
users: {
"userID1": {
"name":"user 1",
"gender": "male"
},
"userID2": {
"name":"user 2",
"gender": "female"
}
}
You can use onAuthStateChanged to detect when the user is logged in, and if it is you can use once() to retrieve user's data
firebaseRef.child('users').child(user.uid).once('value', callback)
Hope it helps
This can be done by directly storing your custom data in Firebase Auth as "custom claims" on each user via the Admin SDK on your backend.
Note this can't be done purely client-side, your server (or you can use a Cloud Function as per the linked guide if you don't already have a server/API set up) needs to make a request through the Admin SDK to securely set the data using the admin.auth().setCustomUserClaims() method:
https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_an_http_request
You could write some code that combines data from firebase auth and firestore document and expose that to the app as a single data entity. To take subscriptions and notify that changes to the whole app, you would be better served with event libraries like Rxjs. Bellow, I wrote the example below using a simple library that implements an event bus.
// auth.js
import { publish } from '#joaomelo/bus'
import { fireauth, firestore } from './init-firebase.js'
const authState = {
userData: null
};
fireauth.onAuthStateChanged(user => {
if (!user) {
authState.userData = null;
publish('AUTH_STATE_CHANGED', { ...authState });
return;
}
// we must be carefull
// maybe this doc does not exists yet
const docRef = firestore
.collection('profiles')
.doc(user.uid);
docRef
// 'set' secures doc creation without
// affecting any preexisting data
.set({}, { merge: true })
.then(() => {
docRef.onSnapshot(doc => {
// the first data load
// and subsequent updates
// will trigger this
authState.userData = {
id: user.uid,
email: user.email,
...doc.data()
};
publish('AUTH_STATE_CHANGED', { ...authState });
});
});
});
// some-place-else.js
import { subscribe } from '#joaomelo/bus'
subscribe('AUTH_STATE_CHANGED',
authState => console.log(authState));
You can expand on that in a post I wrote detailing this solution and also talking about how to update those properties. There is too a small library that encapsulates the answer with some other minor features with code you could check.
This Meteor code displays a message on a headerLabel on a template, the server and/or the client changes the message by inserting a new message in HeaderLabelCol mongo collection and expect the client template to change since it publishes the last inserted document.
I was able to insert a new message using the client browser but did not show till I refreshed the page which may indicate that the reactiveness chain is broken somewhere. What is the problem? How can it be fixed? Thanks
//client.js
Template.header.helpers({
headerLabel: function () {
return HeaderLabelCol.findOne() ? HeaderLabelCol.findOne().headerLabel : 'Make a selection';
}
});
//server.js
HeaderLabelCol = new Mongo.Collection('headerLabelCol');
Meteor.publish('headerLabelCol', function () {
return HeaderLabelCol.find({userId: this.userId}, { sort: { createdAt: -1 } });
});
HeaderLabelCol._ensureIndex({createdAt: -1});
HeaderLabelCol.before.insert(function (userId, doc) {
doc.userId = userId;
doc.createdAt = Date.now();
});
HeaderLabelCol.allow({
insert: function (userId, doc) {
return (userId && doc.owner === userId);
}
});
I think you need to add the condition in your helper as well.
//client.js
Template.header.helpers({
headerLabel: function () {
var result = HeaderLabelCol.findOne({}, { sort: { createdAt: -1 } });
return result ? result.headerLabel : 'Make a selection';
}
});
I have the following code in my Meteor app where I create new users, assign them 'basic' role. Yet I am having a trouble showing on the client side errors returned while processing Accounts.createUser, can someone please tell me how I can return errors returned by Accounts.createUser while having it on the server as my code below. Thanks
/server/users.js
Meteor.methods({
'createMemberAccount': function (data, role) {
var userId;
Meteor.call('createNewAccount', data, function(err, result) {
if (err) {
return err;
}
console.log('New account id: '+ result);
Roles.addUsersToRoles(result, role);
return userId = result;
});
return userId;
},
'createNewAccount': function (adminData) {
return Accounts.createUser({email: adminData.email, password : adminData.password, roles: adminData.roles});
}
});
/client/signup.js
Template.signupForm.events({
'submit #signup-form': function(e, t){
e.preventDefault();
var userData = {};
userData.email = $(e.target).find('[name=email]').val();
userData.password = $(e.target).find('[name=password]').val();
userData.roles = ['basic'];
Meteor.call('createMemberAccount', userData, 'basic', function(err, userId) {
if (!err) {
console.log('All OK');
} else {
console.log('Error: ' + err.message);
}
});
return false;
}
});
Since You are creating an static rol "basic", you don't need to do that pair of methods, and Meteor.calls, instead you can use
So, use the v on the client side, just like this.
Template.register.events({
'submit #register-form' : function(e, t) {
e.preventDefault();
var email = t.find('#account-email').value
, password = t.find('#account-password').value;
// Trim and validate the input
Accounts.createUser({email: email, password : password}, function(err){
if (err) {
// Inform the user that account creation failed
} else {
// Success. Account has been created and the user
// has logged in successfully.
}
});
return false;
}
});
If you see there is not any role yet incude, so now on the server.js use the onCreateUser method.
//Server.js
Accounts.onCreateUser(function(options, user) {
if (options.profile)
user.profile = options.profile;
user.role = "basic"
return user;
});
Now thats is more easy, and with less code, if you are trying to create 2 differents roles like "Admin" and "Basic", just on the client side create a profile field named "profile.roles" and do a if statement on the onCreateUser.
return Accounts.createUser({email: adminData.email, password : adminData.password, roles: adminData.roles});
This part returns the userId once it is created, it doesn't return any errors when it fails.
When it fails, the returned value will be undefined
Also, in the server, we cannot use callbacks with Accounts.createUser
If you want find the errors, you have to use Accounts.createUser in client side.
Coming to this late, but on the server side, you can assign the createUser to a variable and it will return the new user’s _id; then you can check if that exists. For example (server side only):
let email = 'foo#bar.com';
let password = 'bar';
let profile = {firstName: 'foo', lastName: 'bar'};
let newId = Accounts.createUser({
password: password,
email: email,
profile: profile
});
if (!newId) {
// New _id did not get created, reason is likely EMail Already Exists
throw new Meteor.Error(403, "Cannot create user: " + error.reason);
}
else {
// Stuff here to do after creating the user
}
The Meteor.Error line will be passed back as an error in the callback on the client side, so you can reflect that error to the browser.