LoopBack AngularJS extending User model - angularjs

i'm facing some problems when using an extended user model in my AngularJS application.
here is my user.json:
{
"name": "user",
"base": "User",
"strict": false,
"idInjection": true,
"properties": {
"clientType": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
],
"methods": []
}
here is my model-config.json:
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
]
},
"User": {
"dataSource": "mongo"
},
"AccessToken": {
"dataSource": "mongo",
"public": false
},
"ACL": {
"dataSource": "mongo",
"public": false
},
"RoleMapping": {
"dataSource": "mongo",
"public": false
},
"Role": {
"dataSource": "mongo",
"public": false
},
"Store": {
"dataSource": "mongo",
"public": true
},
"user": {
"dataSource": "mongo",
"public": true
}
}
this is my UserCtrl.js
angular.module('app.controllers.user', [])
.controller('UserCtrl', ['user', function (user) {
var vm = this;
vm.addUser = function () {
user.create({
firstName: vm.firstName,
lastName: vm.lastName,
email: vm.email,
password: vm.password,
userType: 'customer'
})
.$promise
.then(function (c) {
console.log('added user: ' + c.email);
});
};
}])
i'm getting the following error:
Error: [$injector:unpr] Unknown provider: userProvider <- user <- UserCtrl
if i use 'User' instead of 'user' it works, but it doesn't use my extended user-model with the specified ACL (READ for everyone)
i've read that you can specify var myUser = app.model.user to make sure that LoopBack uses the extended model. but i don't know how to do that in AngularJS since i specify the model as function parameter in the controller..
can you tell me how to use my extended user model within my AngularJS app?
thanks in advance!!

Do you have your user model generated inside Angular client library? If your application works when you use loopback auto-generated "User" model, then my best guess is that you have created your extended model "user", after you initially generated your angular services. If you are not using grunt task then you should regenerate angular services to update file with all changes and new models that you added since you last time generated the file.
Use lb-ng command to do it. As documentation suggests
For example, if your application has the standard LoopBack project layout, then in the /client sub-directory, enter these commands:
$ mkdir js
$ lb-ng ../server/server.js js/lb-services.js
You can find more information on the following link
http://docs.strongloop.com/display/public/LB/AngularJS+JavaScript+SDK

You need to define a Service, Factory, Provider, Value or Constant called 'user' in order for the service to be injectable in your controller. I do not see either of these in your post.
My suggestion is, if your extended user model is an instance of a class, then use a service:
app.service('user', fn);
If your extended user model is an object literal in JSON format, then use a factory:
app.factory('user', function() { return { ... }; });

Related

How do i modify a raw data object returned by an ExtJS AJAX proxy into a JSON object to be consumed by a Tree Store

In an effort to create a treepanel, i configure it with a treestore whose AJAX proxy url receives json data i have no control of. But using Ext.data.reader.Json's transform property invokable before readRecords executes, gives an option to modify the passed raw (deserialized) data object from the AJAX proxy into a modified or a completely new data object. The transform config, gives the code snippet below:
Ext.create('Ext.data.Store', {
model: 'User',
proxy: {
type: 'ajax',
url : 'users.json',
reader: {
type: 'json',
transform: {
fn: function(data) {
// do some manipulation of the raw data object
return data;
},
scope: this
}
}
},
});
I would please like an example on how to go about modifying the return JSON object
[
{
"id": 3,
"attributes":
{},
"name": "user_one",
"login": "",
"email": "user_one#ats",
"phone": "0751223344",
"readonly": false,
"administrator": false,
"password": null
},
{
"id": 4,
"attributes":
{},
"name": "user_two",
"login": "",
"email": "user_two#ats",
"phone": "0751556677",
"readonly": false,
"administrator": false,
"password": null
}
]
into a JSON object fit for a treestore.
The hierarchical tree is to be rendered to show which user is under which admin using a condition administrator==true from the returned JSON, then a second AJAX request that returns that admin's users shown here.
[
{
"user_id": 3,
"admin_id": 1,
},
{
"user_id": 4,
"admin_id": 2,
}
]
Is the data nested at all? Otherwise why use a treepanel instead of a grid? To your question though, it'll depend on how you configure your treepanel but it would probably be something like this:
transform: {
fn: function(data) {
var treeRecords = Ext.Array.map(data, function(i){
return {
text: i.name,
leaf: true
//any other properties you want
}
});
var treeData = {
root: {
expanded: true,
children: treeRecords
}
};
return treeData;
},
scope: this
}

AngularJS JavaScript SDK (lb-service.js) & ACL : which property for a related model

I am working on a music application, with genres & subgenres.
We use loopback for the backend, and angularJS for the frontend with cordova & ionic.
I cannot manage to authorize via ACL a request that looks like Genre/:id/Subgenre, for which I have a proprety automatically made via lb-service.js : Genre.subGenre()
The relation's type is "hasAndBelongsToMany" for both models Genre & Sub-genres.
Here is my angular controller that I use in the frontend to do the API request :
$scope.genres = [];
Genre.find().$promise.then(function(res) { //this request works : goes to GET /Genres endpoint
$scope.genres = res;
$scope.genres.forEach(function(genre) {
genre.checked=false;
genre.subgenres = [];
Genre.subGenres({id: genre.id}).$promise.then(function(r){ //it is this one that doesn't work, GET /Genres/:id/SubGenre endpoint
genre.subgenres = r;
genre.subgenres.forEach(function(s){
s.checked=false;
});
});
});
});
here is the code in lb-service.js that provide the Genre.subGenre() property:
// INTERNAL. Use SubGenre.genres() instead.
"prototype$__get__genres": {
isArray: true,
url: urlBase + "/SubGenres/:id/genres",
method: "GET"
},
and here is the code in the genre model to authorize the acces to the API for the Genre.subGenre() property :
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "subGenres" // this is to authorize Genre.subGenre(), doesn't works => 401 error.
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "find" // this is to authorize Genre.find(), it works
}
It looks like it is the property that is not correct, because when I do the same thing to authorize the Genre.find() request, it works.
Thank you a lot for your help.
Actually I figured out that I just needed to change in the ACL :
"accessType": "EXECUTE"
into
'"accessType": "READ"

How do I create a correct hasOne relation between two objects with loopback and angular

I've recently started to code in angular as the frontend code and was planning to use loopback (www.loopback.io) as my backend (with a mongodb for my storage).
Now, as a basic idea I'm just creating a login form and register form and will go on from there. Yet, I can't seem to get my models correct, especially the relations between them.
I'd like a user model for the basic username/password stuff, and a person model where I can store more info like firstname, lastname, and more.
I'm currently using the "default" User model that you get out of the box and created a person model with the needed properties.
What I can't get correct though, is the link between the two (a one to one relation, so the HasOne relation).
These are my models:
user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {
"person": {
"type": "hasOne",
"model": "Person",
"foreignKey": "id"
}
},
"acls": [],
"methods": []
}
person.json
{
"name": "Person",
"plural": "Person",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"FirstName": {
"type": "string",
"required": true
},
"LastName": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
my register function
(I'm using the automaticaly generated lb-services file you can extract with the angular SDK provided by loopback, that's why you just see stuff like "User.create" in stead of a "real" api call)
function register(email, password, firstname, lastname) {
var usr = User.create({
email: email,
password: password
}).$promise;
var prs = usr.person.create({
firstname: firstname,
lastname: lastname
});
return usr;
}
There's bound to be a bunch of stuff wrong here, I just don't know what. The user gets created in the database, yet I can't seem to instantly also create a "person" object and link that to the newly created user.
Any help is appreciated, cause I can't seem to find that many resources online that use angular and loopback with the generated lb-services file...
Seems that you reference undefined property person of usr instance.
Create person using User resource, in success callback:
function register(email, password, firstname, lastname) {
var usr = User.create({
email: email,
password: password
}, function() {
User.person.create({ userId: usr.id }, {
firstname: firstname,
lastname: lastname
}, function(person) {
console.log('Persone created:', persone)
})
});
return usr;
}

Loopback, AngularJS and validation

I followed this tutorial to create a project with Loopback and AngularJs. https://github.com/strongloop/loopback-example-angular
Now, I have an application with:
HTML files (with Bootstrap)
AngularJS controllers
AngularJS service (generated with syntax lb-ng server/server.js client/js/services/lb-services.js)
Model (located in ./common folder)
MongoDB backend
The model "Device" is defined in ./common/models/device.js
module.exports = function(Device) {
};
And in ./common/models/device.json
{
"name": "Device",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string",
"required": true
},
"category": {
"type": "string",
"required": true
},
"initialDate": {
"type": "date"
},
"initialPrice": {
"type": "number",
"required": true
},
"memory": {
"type": "number"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
In the "AddDeviceController", I have an initialization part with:
$scope.device = new DeviceToBuy({
name: '',
description: '',
category: '',
initialPrice: 0,
memory: 8
initialDate: Date.now()
});
And I am able to save the $scope.device when executing the following method:
$scope.save = function() {
Device.create($scope.device)
.$promise
.then(function() {
console.log("saved");
$scope.back(); // goto previous page
}, function (error) {
console.log(JSON.stringify(error));
});
}
When everything is valid, the model is saved in the backend. If something is not valid in the $scope.device, I receive an error from my backend. So everything is working fine.
Now, I would like to use the model to perform client-side validation before sending my model to the backend and put some "error-class" on the bootstrap controls.
I tried something in the $scope.save function before sending to the backend:
if ($scope.device.isValid()) {
console.log("IsValid");
} else {
console.log("Not Valid");
}
But I get an exception "undefined is not a function" --> isValid() doesn't exist.
And I cannot find any example on how to execute this client-side validation.
LoopBack models are unopinionated and therefore do not provide client side validation out-of-box. You should use Angular validation mechanisms before calling $save.

'UI-Router-Extras' - Can't Attach / Add New 'FutureState' Objects After App.Config() Has Already Loaded

I am having problems with adding new states to the runtime phase of my app using 'UI-Router-Extras'.
I have been trying for quite some time now to get new states attached and loaded AFTER a user has successfully authenticated using the 'UI-Router-Extras' plugin 'ui-router-extras'
Here is a reference to the 'UI-Router-Extras' Examples for the FutureState documentation that i'm using, but I feel like maybe my scenario is either slightly different that what's shown or I'm missing something altogether.
EXAMPLE CODE IN PLUNKER - CLICK LOGIN BUTTON -> http://plnkr.co/edit/PQwNQcLNMyPfpke076yy
Code Below Loads and Works:
I was successful in getting the initial app.config() lazy loaded from an external file. Like the code describes below:
PUBLIC - External Initially loaded Routes using 'UI-Router-Extras' - 'lazyload/states/public-states.json'
[
{
"name": "unauth",
"url": "/",
"controller": "LoginCtrl",
"templateUrl": "app/components/core/login/login.html",
"roles": ["public"]
},
{
"name": "otherwise",
"url": "/",
"roles": ["public"]
}
]
Initial App Load - Successful Lazy Load of Public States To Begin with On a Login:
'use strict';
var app = angular.module('app', ['ui.router', 'ct.ui.router.extras'])
.config(function ($urlRouterProvider, $stateProvider, $futureStateProvider) {
app.stateProvider = $stateProvider; // MIGHT NEED REFERNCE LATER?
app.futurestateProvider = $futureStateProvider; // MIGHT NEED REFERNCE LATER?
// ASYNC LOAD OF ROUTES AVAILABLE - SOON TO BE BY ROLE
var futureStateResolve = ["$http", function($http) {
return $http.get("lazyload/states/public-states.json").then(function(response) {
angular.forEach(response.data, function(state) {
$stateProvider.state(state);
console.log(state.roles);
})
})
}];
$futureStateProvider.addResolve(futureStateResolve);
console.log($futureStateProvider);
});
Code Below Does NOT work
Below is my code for the part that does not work:
PRIVATE - External Initially loaded Routes using 'UI-Router-Extras' - 'lazyload/states/public-states.json'
This json below is meant to be added after user login using lazy loading and FutureStates. So far no luck :(
[
{
"name": "app",
"abstract": true,
"url": "?menu1State",
"templateUrl": "app/components/core/layout/layout.html",
"controller": "LayoutCtrl",
"roles": ["level1"]
},
{
"name": "app.dashboard",
"url": "/app",
"views": {
"feature": {
"templateUrl": "app/components/core/features/features.html",
"controller": "FeatureCtrl"
}
},
"roles": ["level1"]
},
{
"name": "app.associations_beanbags",
"url": "/app/associations/bean-bags?myParam1&myParam2&modalState",
"views": {
"feature": {
"templateUrl": "app/components/core/features/associations/bean-bags.html",
"controller": "BeanbagsCtrl"
}
},
"roles": ["level2"]
}
]
Login button triggering the lazy creation of states after successful authentication:
<a id="btn-fblogin" href="" class="btn btn-primary pull-right" ng-click="callNotify(username, password);">Login</a>
What happens is when a user clicks on a login button it mocks the success and calls '$scope.callNotify' triggering the code you see below. What ends up happening is everything works up until the 'app.futurestateProvider.futureState(newState);' and the trying to call the new state to see if was added '$state.go('app.dashboard');'. All of this results in an error that states the following:
Console Error:
Error: Could not resolve 'app.dashboard' from state 'unauth'
at Object.transitionTo (http://localhost:3000/bower_components/angular-ui-router/release/angular-ui-router.js:2521:17)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:136:34)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:874:55)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:1301:48)
at Object.go (http://localhost:3000/bower_components/angular-ui-router/release/angular-ui-router.js:2454:21)
at http://localhost:3000/app/components/core/auth/auth-service.js:58:13
at http://localhost:3000/bower_components/angular/angular.js:8113:11
at wrappedCallback (http://localhost:3000/bower_components/angular/angular.js:11573:81)
at wrappedCallback (http://localhost:3000/bower_components/angular/angular.js:11573:81)
at http://localhost:3000/bower_components/angular/angular.js:11659:26
So what this looks like to me is that the states never really got added when we expected, thus causing an error saying something to the likes of "Sorry, i don't see the route you are looking for."
'use strict';
app.controller('AuthServiceTestCtrl', ['$window','$scope','$http', '$state', function (win, $scope, $http, $state) {
$scope.callNotify = function(username,password, $stateProvider, $futureStateProvider) {
//notify(username, password); // CALL SERVICE AND GET A RETURN VALUE / ACTION
var loadedAgain = $http.get("lazyload/states/private-states.json").success(function(response) {
if(username == "testuser#example.com" && password == "abc123"){
console.log('Valid Credentials. Logging In.');
// NOW THAT USER IS LOGGED IN REGISTER NEW PRIVATE ROUTE / STATES - PREFERABLY FROM THE SECURED SERVER FILE ('private-states.json') ABOVE
var adminModuleFutureStates = [
{
"name": "app",
"abstract": true,
"url": "?menu1State",
"templateUrl": "app/components/core/layout/layout.html",
"controller": "LayoutCtrl",
"roles": ["level1"]
},
{
"name": "app.dashboard",
"url": "/app",
"views": {
"feature": {
"templateUrl": "app/components/core/features/features.html",
"controller": "FeatureCtrl"
}
},
"roles": ["level1"]
},
{
"name": "app.associations_bean-bags",
"url": "/app/associations/bean-bags?myParam1&myParam2&modalState",
"views": {
"feature": {
"templateUrl": "app/components/core/features/associations/bean-bags.html",
"controller": "BeanBagsCtrl"
}
},
"roles": ["level2"]
}
];
angular.forEach(adminModuleFutureStates, function(newState) {
console.log(newState);
app.futurestateProvider.futureState(newState); // WAS SAVED AS A VAR IN APP CONFIG FOR LATER REFERENCE
});
// FINALLY GO TO ONE OF THE NEWLY ADDED PRIVATE STATES WE JUST ADDED WHICH IS A DASHBOARD
$state.go('app.dashboard');
}
});
};
}]);
I'm very sorry for not having a working example ready, I'm on it right now, but i figured i'd post this now to show what I do have and possibly get a discussion or solution as to how I can load states to ui-router at runtime via my controller listed above after the application has already loaded the config etc..
What I'm trying to ultimately do here is this:
I really need to only expose two safe public routes to begin with on login. Then once a user logs in the previous public routes stay and i'm trying to add or decorate the existing routes with new ones that now allow the user to only have access to the routes their role provides. Security for us is extremely important and I do not see any benefit whatsoever loading every possible route upfront on a login page letting someone know what out api or server routs are without at least being logged in.
I'm very sorry for rambling but I've come to the conclusion that I'm just flat doing it wrong and need some extra eyes to maybe catch why i can't add new states post load.
Seriously thank you so much!
EXAMPLE CODE IN PLUNKER - CLICK LOGIN BUTTON -> http://plnkr.co/edit/PQwNQcLNMyPfpke076yy
I decided to use the $ocLazyLoad service instead but still using an app injector to add routes dynamically after the app has loaded and been initially configured with basic public routes before authentication etc..
angular.module("app").configInjector.invoke(['$stateProvider', function ($stateProvider) {
Then after the app injector was setup and the user was authenticated and a role(s) was validated by the server a json response defining the allowed ui-router views / routes / states are looped over and added dynamically to the ui-router state definitions. These routes are defined in the json but so is any accompanying controllers and views that are lazy loaded using $ocLazyLoad.
Overall what I ended up doing was the following:
angular.module("auth")
.factory('AuthRouteLoaderFactory', ['$window', '$rootScope', '$http', '$state', '$cookieStore','$location', '$timeout','AuthSession',
function(win, $rootScope, $http, $state, $cookieStore, $location, $timeout, AuthSession) {
// PRIVATE INTERFACE
function loadPrivateRoutes() {
console.log('AuthRouteLoaderFactory :: LOADING PRIVATE ROUTES');
var loadedPrivateRoutes = $http.get("lazyload/states/private-states.json").success(function (response) {
angular.module("app").configInjector.invoke(['$stateProvider', function ($stateProvider) {
// VERY IMPORTANT - POST LOGIN ROUTE LOADER / INJECTOR;// DYNAMIC AND SERVER DETERMINED JSON ITERATED BASED ON SPECIFIC ROLES PRE-MADE BY SERVER.
angular.forEach(response, function (state) {
if(!state.abstract){
state.views.feature.resolve[state.views.feature.data.controllerAlias] = ['$ocLazyLoad', function($ocLazyLoad){return $ocLazyLoad.load({"name": state.views.feature.data.controllerAlias,"files": state.views.feature.data.controllerFiles})}];
state.views.feature.resolve.isAuthenticated = function(){
// CHECK IF WE ARE A VALID SESSION FOR EACH LAZY LOAD
//AuthSession.validateToken();
};
}
console.log('AuthRouteLoaderFactory :: loadPrivateroutes state loaded -> ' + state.name);
$stateProvider.state(state.name, state);
});
$state.go('app.dashboard');
}]);
});
}
// FOR NOW WE LOAD ROUTES ANYWAY UNTIL WE CALL API'S /ME OR PING SERVICE, THEN ON SUCCESS WE LOAD ROUTES
if(AuthSession.validateToken()){
$rootScope.hideLoader = true;
loadPrivateRoutes();
console.log('AuthRouteLoaderFactory :: SESSION VALIDATION SUCCESSFUL :: PROCEED');
}
// PUBLIC INTERFACE
return {
testFactory: function() {
console.log('AuthRouteLoaderFactory :: testFactory');
},
isSessionValid: function(){
return $cookieStore.get('fakeLoginSession');
},
invalidateSession: function(){
$cookieStore.remove('fakeLoginSession');
$location.path('/login.html');
},
loadRoutes: loadPrivateRoutes
};
}
]);
Public States / Routes:
[
{
"name": "unauth",
"url": "/",
"controller": "LoginCtrl",
"templateUrl": "app/components/core/login/unauth.html",
"data": {
"roles": ["public"]
}
},
{
"name": "login",
"url": "/login",
"controller": "LoginCtrl",
"templateUrl": "app/components/core/login/unauth.html",
"data": {
"roles": ["public"]
}
},
{
"name": "otherwise",
"url": "/",
"data": {
"roles": ["public"]
}
}
]
Private States / Routes:
[
{ "name": "app", "abstract": true, "url": "", "templateUrl": "app/components/core/layout/layout.html", "controller": "LayoutCtrl" },
{
"name": "app.dashboard",
"views": {
"feature": {
"templateUrl": "lazyload/components/core/features/dashboard/dashboard.html",
"controller": "DashboardCtrl as dashboard",
"resolve": {},
"data": {
"controllerAlias": "dashboard",
"controllerFiles": ["lazyload/components/core/features/dashboard/dashboard-controller.js"]
}
}
}
},
{
"name": "app.associations_role-type",
"views": {
"feature": {
"templateUrl": "lazyload/components/core/features/associations/role-type.html",
"controller": "RoleTypeCtrl as roleType",
"resolve": {},
"data": {
"controllerAlias": "roleType",
"controllerFiles": ["lazyload/components/core/features/associations/role-type-controller.js"]
}
}
}
}
]

Resources