I've configured all users to be created with an empty favorites array: user.favorites: []
Since the users collection is treated differently, how should I publish, subscribe, and access subscribed favorites data in angular-meteor?
Here's what I have so far:
// Meteor.methods ==========================================
addFavorite: function(attendeeId){
var loggedInUser = Meteor.user();
if( !loggedInUser ){
throw new Meteor.Error("must be logged in");
}
loggedInUser.favorites.push(attendeeId);
loggedInUser.username = loggedInUser.username+"x";
console.log(loggedInUser.favorites);
}
// controller ========================================
$scope.addFavorite = function(attendeeId){
$meteor.call("addFavorite", attendeeId);
}
// server =======================================================
Meteor.publish('myFavorites', function(){
if(!this.userId) return null;
return Meteor.users.find(this.userId);
});
Meteor.users.allow({
insert: function(userId, doc){
return true;
},
update: function(useId, doc, fieldNames, modifier){
return true;
},
remove: function(userId, doc){
return true;
}
});
User.favorites is empty. When addFavorite is called, it logs an array with a single userId that doesn't update the mongoDB at all. It looks as if Meteor.user() isn't reactivly updating. Does anyone know what I'm doing wrong? Thank you!
EDIT
Latest iteration of code. Favorites are passed into $scope.favorites but isn't reactive. How do I fix this? Thanks!
// publish
Meteor.publish('myFavorites', function(){
if(this.userId){
return Meteor.users.find(this.userId, {
fields: {
favorites: 1
}
});
}else{
this.ready();
}
});
// subscribe
$meteor.subscribe('myFavorites')
.then(function(subscriptionHandle)
{
var user = $meteor.collection(function(){
return Meteor.users.find({_id: Meteor.userId()});
});
$scope.favorites = user[0].favorites;
});
tldr;
Accounts collection is reactive, but by default only the username, emails, and profile fields are published. The quickest fix is to attach the favorites as a new field on the User.profile object.
// Meteor.methods ==========================================
addFavorite: function(attendeeId){
var loggedInUser = Meteor.user();
if( !loggedInUser ){
throw new Meteor.Error("must be logged in");
}
if (loggedInUser.profile.favorites){
loggedInUser.profile.favorites.push(attendeeId);
}
else {
loggedInUser.profile.favorites = [];
loggedInUser.profile.favorites.push(attendeeId);
}
loggedInUser.username = loggedInUser.username+"x";
console.log(loggedInUser.profile.favorites);
}
Although right now you probably are writing to the user, which you can verify by using meteor mongo --> db.users.find().pretty(), but the subscription does not publish your favorites field.
Alternative approach
Alternatively, you can publish the favorites field
// Server code --------
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'favorites': 1}});
} else {
this.ready();
}
});
Opinionated Meteor.users philosophy
I like to structure my users object around 3 properties:
User.profile --> published to the client, and directly modifiable by the client through client-side code
User.public --> published to the client, but not modifiable except through server-side Meteor methods
User.private --> not published to the client (i.e. only accessible to read on server code), and only modifiable by server code (with client simulation)
Just make sure that when you remove the insecure and autopublish packages that you double-check your Collections security by using the Meteor.users.allow() function in your server code
Run meteor list to if you want to verify whether or not insecure and autopublish packages are being used in your current project. NOTE: By default Meteor does install them when you first create your app)
// Server code --------
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'public': 1}});
} else {
this.ready();
}
});
Related
I have this problem with kinvey backend,
I'm trying to fetch data from my collection but it doesn't work for me. here is my code :
var query = new $kinvey.Query();
query.equalTo('_id', '5909e8084c68b1ef74fa4efc');
var dataStore = $kinvey.DataStore.collection('User1Bases', $kinvey.DataStoreType.Network);
var stream = dataStore.find(query);
stream.subscribe(function onNext(entity) {
// ...
}, function onError(error) {
// ...
}, function onComplete() {
//...
});
Can you help me please
If you let run the code you have posted then consider four things:
Make sure you have Kinvey implemented:
<script src="https://da189i1jfloii.cloudfront.net/js/kinvey-html5-sdk-3.10.2.min.js"></script>
Make sure you have initialized the Kinvey service before:
// Values shown in your Kinvey console
Kinvey.init({
appKey: '<your_appKey>',
appSecret: 'your_appSecret'
});
Make sure you are logged in with a user that has the rights to read your collection (should be fine using the All Users role (default)):
var promise = Kinvey.User.login('<username>', '<password>')
.then(function() {
console.log ("You are logged in");
})
.catch(function(error) {
console.log (error);
});
Output the return result to see whats coming back. To make sure you do the query AFTER successful login, paste you query inside the .then function of login.
I'm not sure if your query is valid unter 3.x since a lot has changed and I'm not working with older Kinvey versions.
So that all together would look like this:
// Initialize Kinvey
Kinvey.init({
appKey: '<your_appKey>',
appSecret: 'your_appSecret'
});
// Login with already registered user
var promise = Kinvey.User.login('<username>', '<password>')
.then(function() {
console.log ("You are logged in");
// Your query
var query = new $kinvey.Query();
query.equalTo('_id', '5909e8084c68b1ef74fa4efc');
var dataStore = $kinvey.DataStore.collection('User1Bases', $kinvey.DataStoreType.Network);
var stream = dataStore.find(query);
stream.subscribe(function onNext(entity) {
// Output of returning result
console.log (entity);
// ...
}, function onError(error) {
// ...
}, function onComplete() {
//...
});
})
.catch(function(error) {
console.log (error);
});
There are now three return sets possible:
Nothing (as you say) -> Something missing/wrong in the code (compare yours with mine)
Empty array: Your query didn't find anything, adapt the search value(s)
One or more entries in the array -> All fine, what you were looking for!
Hope that helps!
When querying by _id there is a built in method: http://devcenter.kinvey.com/angular/guides/datastore#FetchingbyId
Try switching to var stream = dataStore.findById('entity-id');
Also check to make sure you don't have any preFetch or postFetch BL that is interfering with the query.
I have list of users for chat purpose, something like on facebook where i got all users from my database using ngResource. When user is offline i got red marker close to his name and when is online i use green marker.
What i want to archieve is that when user sign in, my red marker will turn into green. When user login into my app, my Hub method OnConnected() gets fired and call my client side code
Hub method when user sign in.
#region Connect
public override Task OnConnected()
{
var userDetails = new ApplicationUser
{
ConnectionId = Context.ConnectionId,
UserName = Context.Request.GetHttpContext().User.Identity.Name,
Id = HttpContext.Current.User.Identity.GetUserId(),
};
if (onlineUsers.Count(x => x.ConnectionId == userDetails.ConnectionId) == 0)
{
onlineUsers.Add(new ApplicationUser {
ConnectionId = Context.ConnectionId,
UserName = userDetails.UserName,
Id = userDetails.Id,
});
Clients.All.newOnlineUser(userDetails);
Clients.Caller.getOnlineUsers(onlineUsers);
}
return base.OnConnected();
}
#endregion
Client side code in my controller
$scope.online_users = UserService.getChatUsers();
PrivateChatService.addOnlineUser(function (user) {
angular.forEach($scope.online_users, function (value, key) {
if (user.UserId == value.Id) {
value.Active = true;
}
});
console.log("newOnlineUser finished");
});
Problem is with forEach method in my client side code. In that time when my signalR hub fires my method ".addOnlineUser" my $scope.online_users is not resolved so i only have promise but not data so i cant iterate through that array to change user status from offline to online. Is something how i can wait for promise to be resolved?
Update:
I had something like this but this is not definitely good aproach since i hit all the time my database to get users.`
PrivateChatService.addOnlineUser(function (user) {
var dataPromise = UserService.getChatUsers(function(response){
$scope.online_users = response;
angular.forEach(dataPromise, function (value, key) {
if (user.UserId == value.Id) {
value.Active = true;
}
});
});
console.log("newOnlineUser finished");
});
I am very new to Ionic Framework. I am learning the framework and have tried to build a simple android app, which displays a simple list using json. Now, I want add a favorite list which will show user selected items in it. When user clicks on a button it should add that item in a favorite list. And When user click on Favorite tab it should show list of all favorite items.
At present I am trying to do this with simple json and global controller. But I am afraid if this is used on android app on a phone it will not store all favorites, it would remove all favourites once app is closed. Can anyone please suggest a better approach towards it.
Many thanks in advance.
I see you tagged the question with local storage, so why not use that? Also, you could use one of the popular mBaaS solutions like Firebase or gunDB.
As for the logic, it's quite easy: you create a new array which you use for storing these favorites (you handle the adding/removing on the favorite button click). You then use the ng-repeat on the Favorites tab to list the favorites array.
The best way to do this would be pouchdb, i m using in same way.!
Install pouchdb using command:
bower install pouchdb
add below line in index.html
<script src="lib/pouchdb/dist/pouchdb.min.js"></script>
make a service:
.factory('FavService', function (UserService) {
var FavService = {};
var localDB;
var user = UserService.getUser();
if (user) {
localDB = new PouchDB('u_' + user.id);
}
FavService.configDbs = function () {
//console.log('config dbs');
var user = UserService.getUser();
if (user) {
localDB = new PouchDB('u_' + user.id);
}
};
FavService.storeToLocal = function (product) { //change function name
if (localDB && product !== "") {
localDB.post(product);
// console.log("Action completed");
} else {
// console.log("Action not completed");
}
};
FavService.getLocalList = function (callback) {
if (localDB) {
localDB.allDocs({
include_docs: true
}).then(function (response) {
// console.log("response :"+JSON.stringify(response));
localDB = response.rows;
callback(response.rows);
}).catch(function () {
callback(null);
});
} else {
FavService.configDbs();
}
};
});
I have recently followed a tutorial over on Thinkster for creating a web app using Angular and Firebase.
The tutorial uses the Firebase simpleLogin method allows a 'profile' to be created that includes a username.
Factory:
app.factory('Auth', function($firebaseSimpleLogin, $firebase, FIREBASE_URL, $rootScope) {
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register: function(user) {
return auth.$createUser(user.email, user.password);
},
createProfile: function(user) {
var profile = {
username: user.username,
md5_hash: user.md5_hash
};
var profileRef = $firebase(ref.child('profile'));
return profileRef.$set(user.uid, profile);
},
login: function(user) {
return auth.$login('password', user);
},
logout: function() {
auth.$logout();
},
resolveUser: function() {
return auth.$getCurrentUser();
},
signedIn: function() {
return !!Auth.user.provider;
},
user: {}
};
$rootScope.$on('$firebaseSimpleLogin:login', function(e, user) {
angular.copy(user, Auth.user);
Auth.user.profile = $firebase(ref.child('profile').child(Auth.user.uid)).$asObject();
console.log(Auth.user);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
console.log('logged out');
if (Auth.user && Auth.user.profile) {
Auth.user.profile.$destroy();
}
angular.copy({}, Auth.user);
});
return Auth;
});
Controller:
$scope.register = function() {
Auth.register($scope.user).then(function(user) {
return Auth.login($scope.user).then(function() {
user.username = $scope.user.username;
return Auth.createProfile(user);
}).then(function() {
$location.path('/');
});
}, function(error) {
$scope.error = error.toString();
});
};
At the very end of the tutorial there is a 'next steps' section which includes:
Enforce username uniqueness-- this one is tricky, check out Firebase priorities and see if you can use them to query user profiles by username
I have searched and searched but can't find a clear explanation of how to do this, particularly in terms of the setPriority() function of Firebase
I'm quite the Firebase newbie so any help here would be gratefully recieved.
There are a few similar questions, but I can't seem to get my head around how to sort this out.
Enormous thanks in advance.
EDIT
From Marein's answer I have updated the register function in my controller to:
$scope.register = function() {
var ref = new Firebase(FIREBASE_URL);
var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username);
q.once('value', function(snapshot) {
if (snapshot.val() === null) {
Auth.register($scope.user).then(function(user) {
return Auth.login($scope.user).then(function() {
user.username = $scope.user.username;
return Auth.createProfile(user);
}).then(function() {
$location.path('/');
});
}, function(error) {
$scope.error = error.toString();
});
} else {
// username already exists, ask user for a different name
}
});
};
But it is throwing an 'undefined is not a function' error in the line var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username);. I have commented out the code after and tried just console.log(q) but still no joy.
EDIT 2
The issue with the above was that the Thinkster tutorial uses Firebase 0.8 and orderByChild is available only in later versions. Updated and Marein's answer is perfect.
There are two things to do here, a client-side check and a server-side rule.
At the client side, you want to check whether the username already exists, so that you can tell the user that their input is invalid, before sending it to the server. Where exactly you implement this up to you, but the code would look something like this:
var ref = new Firebase('https://YourFirebase.firebaseio.com');
var q = ref.child('profiles').orderByChild('username').equalTo(newUsername);
q.once('value', function(snapshot) {
if (snapshot.val() === null) {
// username does not yet exist, go ahead and add new user
} else {
// username already exists, ask user for a different name
}
});
You can use this to check before writing to the server. However, what if a user is malicious and decides to use the JS console to write to the server anyway? To prevent this you need server-side security.
I tried to come up with an example solution but I ran into a problem. Hopefully someone more knowledgeable will come along. My problem is as follows. Let's say your database structure looks like this:
{
"profiles" : {
"profile1" : {
"username" : "Nick",
"md5_hash" : "..."
},
"profile2" : {
"username" : "Marein",
"md5_hash" : "..."
}
}
}
When adding a new profile, you'd want to have a rule ensuring that no profile object with the same username property exists. However, as far as I know the Firebase security language does not support this, with this data structure.
A solution would be to change the datastructure to use username as the key for each profile (instead of profile1, profile2, ...). That way there can only ever be one object with that username, automatically. Database structure would be:
{
"profiles" : {
"Nick" : {
"md5_hash" : "..."
},
"Marein" : {
"md5_hash" : "..."
}
}
}
This might be a viable solution in this case. However, what if not only the username, but for example also the email has to be unique? They can't both be the object key (unless we use string concatenation...).
One more thing that comes to mind is to, in addition to the list of profiles, keep a separate list of usernames and a separate list of emails as well. Then those can be used easily in security rules to check whether the given username and email already exist. The rules would look something like this:
{
"rules" : {
".write" : true,
".read" : true,
"profiles" : {
"$profile" : {
"username" : {
".validate" : "!root.child('usernames').child(newData.val()).exists()"
}
}
},
"usernames" : {
"$username" : {
".validate" : "newData.isString()"
}
}
}
}
However now we run into another problem; how to ensure that when a new profile is created, the username (and email) are also placed into these new lists? [1]
This in turn can be solved by taking the profile creation code out of the client and placing it on a server instead. The client would then need to ask the server to create a new profile, and the server would ensure that all the necessary tasks are executed.
However, it seems we have gone very far down a hole to answer this question. Perhaps I have overlooked something and things are simpler than they seem. Any thoughts are appreciated.
Also, apologies if this answer is more like a question than an answer, I'm new to SO and not sure yet what is appropriate as an answer.
[1] Although maybe you could argue that this does not need to be ensured, as a malicious user would only harm themselves by not claiming their unique identity?
I had a similar problem. But it was after registering the user with password and email. In the user profile could save a user name that must be unique and I have found a solution, maybe this can serve you.
Query for username unique in Firebase
var ref = new Firebase(FIREBASE_URL + '/users');
ref.orderByChild("username").equalTo(profile.username).on("child_added", function(snapshot) {
if (currentUser != snapshot.key()) {
scope.used = true;
}
});
ref.orderByChild("username").equalTo(profile.username).once("value", function(snap) {
//console.log("initial data loaded!", Object.keys(snap.val()).length === count);
if (scope.used) {
console.log('username already exists');
scope.used = false;
}else{
console.log('username doesnt exists, update it');
userRef.child('username').set(profile.username);
}
});
};
How do you use angularjs service to call pouchdb and return the data to the controller? I have been working on a ionic app with pouchdb for local storage. I have a simple crud app built in a controller. Now I want to start to move the pouchdb calls into a service. I haven’t been able to get back data from the service. How would I use a service to call pouchdb to get all docs and return it to the controller?
One strategy that I think could work very well for Angular services is this one. It describes a method for keeping an in-memory array synced with the result of PouchDB's allDocs().
Since it's an array that automatically stays synced with PouchDB, you can just do an ng-repeat on it, and you're done. :)
Although your question is a year old, it deserves an answer.
You might want more than one service i.e. one to use in the controller and another for the backend database storage. For example, in the controller:
(function () {
'use strict';
angular
.module('app.services')
.factory('db',db);
db.$inject = ['$db'];
function db($db) {
var data = {}; // set up a data object to receive document(s)
return {
getDoc: getDoc,
getList: getList,
save: save,
saveBatch: saveBatch
};
// get a single document using the id
function getDoc(id) {
$db.getDoc(id)
.then(
function onSuccess(doc) {
// success so update the view model
angular.extend(data,doc); // use angular.extend to shallow copy object so that it can be returned in full
},
function onError() {
// failure to get document
}
);
return data;
}
// retrieve a group of documents where key is the prefix of the data you want
function getList(key) {
$db.getList(key).then(
function onSuccess(docs) {
// success so update the view model details
angular.forEach(docs.rows, function (value) {
this.push(value.doc);
}, data);
// now you can sort data or anything else you want to do with it
},
function onError() {
// no data found
}
);
return data;
}
// save a single viewItem
function save(viewItem) {
$db.update(viewItem).then(
function onSuccess() {
// success so update view model if required
},
function onError(e) {
console.log(e); // unable to save
}
);
}
// save an array of viewItems
function saveBatch(viewItems) {
$db.updateBatch(viewItems).then(
function onSuccess() {
// success so update the view model if required
},
function onError(e) {
console.log(e); // unable to save
}
);
}
}
})();
For the backend, something like this:
(function () {
'use strict';
angular
.module('app.services')
.factory('$db',$db);
$db.$inject = ['$q'];
function $db($q) {
var db;
return {
setLocalDB: setLocalDB,
update: update,
updateBatch: updateBatch,
getDoc: getDoc,
getAllDocs: getAllDocs,
getList: getList
};
// ------ DATABASE OPENING HANDLER(S) ------
// set to any named database
function setLocalDB(dbName) {
db = new PouchDB(dbName);
return db.info()
.catch(failedCheck()); // returns a promise to either work or fail
}
// return a rejection for a failure
function failedCheck() {
return $q.reject();
}
// ------ DOCUMENT HANDLING ------
// update document but if errors occur recurse qUpdate until either complete or retries exhausted
function update(doc) {
var counter = 0;
return $q.when(qUpdate(doc,counter));
}
// this routine works for both new and existing documents
function qUpdate(doc,counter) {
return db.put(doc)
.then(function() {
console.log('success - new document');
})
.catch(function(e) {
console.log(e); // not a new document so try as a revision of existing document using _id to find
return db.get(doc._id)
.then(function(origDoc) {
doc._rev = origDoc._rev; // get document revision _rev
return db.put(doc,doc._id,doc._rev)
.then(function() {
console.log('success - revision of document');
})
.catch(function(e){
console.log(e); // log error for failure
});
})
.catch(function(e){
console.log(e); // log error before we take any other action
counter ++; // increment counter, so we can limit retries (5 by default)
if (counter< 5) {
switch (e.status) {
case 404:
delete doc._rev; // remove revision information so we can see if this works
return qUpdate(doc); // might be deleted so return revised document for retry
case 409:
return qUpdate(doc); // in conflict so try again
default:
try {
throw new Error("cannot save: " + doc._id); // cannot go any further so throw new error
} catch(err) {
console.log(err); // log error for failure
}
}
} else {
try {
throw new Error("cannot save" + doc._id); // cannot go any further so throw new error
} catch(err) {
console.log(err); // log error for failure
}
}
});
});
}
// update a document batch stored in an array
function updateBatch(docs) {
return $q.when(qUpdateBatch(docs));
}
// do the actual update of a batch
function qUpdateBatch(docs) {
db.bulkDocs(docs).then(function(res) {
for (var i=0; i < res.length; i++) {
if (res[i].status === 409) {
update(docs[i]); // in conflict so try this document separately
}
}
}).catch(function(e){
console.log(e); // log error
});
}
// get the document as an angular promise and deal with it in host routine
function getDoc(id) {
return $q.when(db.get(id));
}
// get all documents
function getAllDocs() {
return $q.when(db.allDocs({include_docs: true, attachments: false}));
}
// get a batch of documents between a start and end key
function getList(key) {
return $q.when(db.allDocs({startkey: key, endkey: key + '\uffff', include_docs: true, attachments: false}));
}
}
})();
In your main controller you would want to set the database:
$db.setLocalDB('yourDB');
Hope this is what you were looking for?
In my own data services module I have other functions for remote database, event listeners, remove, sync, compact, destroy and so on.