I am am trying to do a simple restful put command. My problem is that I need to do a put command into a different end point to my store.
I have my rest adapter
DS.RESTAdapter.reopen({
namespace: 'datastore'
});
I need to be able to call an end point, but not sure how to do it;
Something like,
store('foundItems', JSON)
Where foundItems is the end point.
you'll want to create a custom RESTAdapter and override buildURL, this should help you start
App.PeopleAdapter = DS.RESTAdapter.extend({
host: 'http://www.google.com',
namespace: 'api/v1',
/**
Builds a URL for a given type and optional ID.
By default, it pluralizes the type's name (for example,
'post' becomes 'posts' and 'person' becomes 'people').
If an ID is specified, it adds the ID to the path generated
for the type, separated by a `/`.
#method buildURL
#param {String} type
#param {String} id
#returns String
*/
buildURL: function(type, id) {
var url = [],
host = get(this, 'host'),
prefix = this.urlPrefix();
if (type) { url.push(this.pathForType(type)); }
if (id) { url.push(id); }
if (prefix) { url.unshift(prefix); }
url = url.join('/');
if (!host && url) { url = '/' + url; }
return url;
},
});
Related
I am building a javascript Application. This application is using a plugin called jq-router. When I do something like the following, the address bar changes but the view fails to change.
$.router.onRouteBeforeChange(function(e, route, params){
firebase.auth().onAuthStateChanged(function(user){
if(!user && route.protected) {
$.router.go('landing', {}); <!-- This line calls the plugin.
}
});
});
The function in the plugin that is called above looks like the following.
/**
* Navigates to given route name & params
* #params {string} routeName
* #params {object} params
* #return {object} this
*/
s.go = function(routeName, params) {
var s = this;
paramSrv.setParams(params);
window.location = s.href(routeName, params);
return s;
};
You can access the entire plugin here: https://github.com/muzammilkm/jq-router
Again, expected result is that the view and the address bar update. Currently the only thing updating is the address bar, the view still renders.
You should be using onViewChange/onRouteChanged events & also exclude the landing route avoid circular loop. onRouteBeforeChange event is intended to notify the route is about to change, so that if any clean up operation can be done.
$.router.onViewChange(function(e, viewRoute, route, params){
firebase.auth().onAuthStateChanged(function(user){
if(!user && route.name !== "landing" && route.protected) {
$.router.go('landing', {}); <!-- This line calls the plugin.
}
});
});
When I try to call a transaction on an asset that is inherited from a abstract base class asset, the call fails with Error: Invalid or missing identifier for Type <type> in namespace <name.spa.ce>
user.cto
namespace com.aczire.alm.base.user
import com.aczire.alm.base.*
abstract participant User {
o String uid
o String title optional
o String firstName optional
o String lastName optional
o UserTransactionLogEntry[] logEntries
}
concept UserTransactionLogEntry {
//--> User modified_by
o String comment optional
o DateTime timestamp
}
abstract transaction UserTransaction {
o String comment
}
abstract event UserTransactionEvent {
o String comment
}
admin.cto
namespace com.aczire.alm.base.user.admin
import com.aczire.alm.base.*
import com.aczire.alm.base.user.*
participant Admin identified by uname extends User {
o String uname
}
abstract transaction AdminUserTransaction extends UserTransaction {
o Admin user
--> Admin modified_by
}
abstract event AdminUserTransactionEvent extends UserTransactionEvent {
--> Admin user
--> Admin modified_by
}
transaction CreateUser extends AdminUserTransaction {
}
admin.js
/**
* Create a User
* #param {com.aczire.alm.base.user.admin.CreateUser} createUser - the CreateUser transaction
* #transaction
*/
function createUser(newuser) {
console.log('createUser');
var factory = getFactory();
var NS_AU = 'com.aczire.alm.base.user.admin';
var user = factory.newResource(NS_AU, 'Admin', newuser.uname);
user.uid = newuser.uid;
// save the order
return getAssetRegistry(NS_AU)
.then(function (registry) {
return registry.add(user);
})
.then(function(){
var userCreatedEvent = factory.newEvent(NS_AU, 'UserCreatedEvent');
userCreatedEvent.user = user;
userCreatedEvent.comment = 'Created new admin user - ' + newuser.uname + '!';
emit(userCreatedEvent);
});
}
I tried making the parameters to the TP as User, Admin; moving the transaction arguments to base class as User type; moving the transaction to the base class etc.. But nothing seems to work.
Does the inheritance works differently here?
Error shown in composer playground.
Your admin.js has some issues. The error is because Admin is a participant so you have to getParticipantRegistry() not Assetregistry and then add the user. Also in your model file admin.cto there is no event named UserCreatedEvent. So you first need to add it to the model and then emit the event. To add the user try changing to this.
/**
* Create a User
* #param {com.aczire.alm.base.user.admin.CreateUser} createUser - the CreateUser transaction
* #transaction
*/
function createUser(newuser) {
console.log('createUser');
var NS_AU = 'com.aczire.alm.base.user.admin';
var factory = getFactory();
var testuser = factory.newResource(NS_AU, 'Admin', newuser.user.uname);
testuser.uid=newuser.user.uid
testuser.logEntries=newuser.user.logEntries
// save the order
return getParticipantRegistry('com.aczire.alm.base.user.admin.Admin')
.then(function (registry) {
return registry.add(testuser);
});
}
I have a Angular 1.5.9 web application and a Node.js/Sails.js 0.12 backend.
Inside Angular runs UI router 0.4 to handle states.
The state definitions might look like this (quite vanilla, I'd say):
$stateProvider.state('dogs', {
url: '/ourdogsarecute_{specialIDofDog}
}).
state('dogs.specialDogState', {
url: '/specialinfo_{specialInfoOfDog}'
});
Now, the following situation arises: In the backend (i.e. outside of Angular), I have to transform an Angular UI router state link like
{stateName: 'dogs.specialDogState', stateParams: {specialIDofDog: 11212, specialInfoOfDog: 'likesbones' } } into a valid URL like https://www.our-app.dog/ourdogsarecute_11212/specialinfo_likesbones.
I have no idea how to do that without a lot of manual work. Is there a kind of parser for UI router states as a node module?
I can access the front-end code where the state definitions lie from the backend somehow. that's not the problem. The problem is the transformation from state links into URLs.
UI-Router 1.0 split the code up into ui-router core and ui-router angularjs. You can use ui-router core (which has no external dependencies) on your node backend to generate these urls. Since you already have your states available as a JSON file, you can simply register the states with ui-router core in your backend and then use the state objects to generate URLs.
In your node backend, add ui-router core
npm install --save #uirouter/core
// The library exports most of its code
var UIR = require('#uirouter/core');
// Create the router instance
var router = new UIR.UIRouter();
// Get the state registry
var registry = router.stateRegistry;
var states = [
{ name: 'dogs', url: '/ourdogsarecute_{specialIDofDog}' },
{ name: 'dogs.specialDogState', url: '/specialinfo_{specialInfoOfDog}' },
];
states.forEach(state => registry.register(state));
var params = { specialIDofDog: '11212', specialInfoOfDog: 'lovesbones' };
// Get the internal state object
var stateObj = registry.get('dogs.specialDogState').$$state();
// Generate the URL
console.log(stateObj.url.format(params));
For reference: my solution now looks like this.
First of all, I've put my state definitions into a separate file, making it easier to access it from outside:
var myStates = [
{
name: 'dogs', stateProperties: {
url: '/ourdogsarecute_{specialIDofDog}'
}
}, {
name: 'dogs.specialDogState', stateProperties: {
url: '/specialinfo_{specialInfoOfDog}'
}
}];
And then in my app.config
for(var i = 0; i < myStates.length; i++) {
$stateProvider.state(myStates[i].name, myStates[i].stateProperties);
}
In the backend, I've created this function:
/**
* #description Turns state name and state params into a URL string, using stateLinks definition synchronized from front end (being UI router state definitions)
* #param {string} stateName Something like 'dogs.info.specialAttributes'
* #param {object} stateParams Something like {dogID: 34346346, dogStatus: 'private', dogInfo: 'food'}
* #returns {string} URL
*/
stateLinkResolve: function(stateName, stateParams) {
if(!(stateName && stateName.length > 0)) {
return '/';
}
var resultUrl = '';
var splittedSubStates = stateName.split('.');// split "dogs.info.specialAttributes" into ["dogs","info","specialAttributes"]
var currentStateInHierarchy = '';
for(var i = 0; i < splittedSubStates.length; i++) {
/* Add dot if "in between": not the first, not the last. So that "dogs" remains "dogs", but when going to "dogs.info", we want the dot in between */
if(i > 0 && i < (splittedSubStates.length + 1) ) {
currentStateInHierarchy += '.';
}
currentStateInHierarchy += splittedSubStates[i]; // Add current splitted name (being only the last name part) to the state name in its context. I.e. when doing "info", we want to access "dogs.info"
var currState = _.find(stateDefs,{name: currentStateInHierarchy});
var urlRaw = currState.stateProperties.url;
/* uiRouter URLs may contain wildcards for parameter values like /ourdogs_{dogID:int}_{dogStatus}/:dogInfo.
We go through each of these three types and replace them with their actual content.
*/
for(var currentParam in stateParams) {
urlRaw = urlRaw.replace(':' + currentParam, stateParams[currentParam]); // search for ":paramName" in URL
urlRaw = urlRaw.replace('{' + currentParam + '}', stateParams[currentParam]); // search for "{paramName}" in URL
// search for "{paramName:paramType}" in URL
var uiRouterParamTypes = ["hash", "string", "query", "path", "int", "bool", "date", "json", "any"];
for(var j = 0; j < uiRouterParamTypes.length; j++) {
urlRaw = urlRaw.replace('{' + currentParam + ':' + uiRouterParamTypes[j] + '}', stateParams[currentParam]);
}
}
resultUrl += urlRaw;
}
return resultUrl;
}
The problem is: This might fail for edge cases and it will definitely fail for new features being implemented to UI state router and the way URLs are built there. So, still hoping for a solution which directly uses UI router magic.
I have a model: api/models/Agency.js
attributes: {
// attributes for agency
// One agency can have many owners
owners: {
collection: 'user',
required: true,
via: 'agencies'
},
employees: {
collection: 'user',
via: 'employer'
}
}
The model has a many to many relationship with Users; as many Users can own many Agencies.
I want to protect the Blueprint controller for Agency (AgencyController.js) with a Policy called isOwner.js; which makes sure the User is the owner of the Agency before allowing them to make an edit. I have based the policy on the example found in the Sails.js docs, where I am trying to ensure the userId (found in the session) is the owner of the resource.
api/policies/isOwner.js:
module.exports = function(req, res, next) {
var userId = req.session.passport.user;
req.options.where = req.options.where || {};
req.options.where.owners = userId;
return next();
};
Then in my config/policies.js file I have added the following:
AgencyController: {
destroy: ['isOwner'],
update: ['isOwner']
},
This doesn't work. I think its because of the many to many relationship between the two models. My question is can I create a policy that can query a many to many relationship? Or is it only possible through a one to many relationship?
Thanks.
I couldn't find a way of nicely protecting the blueprint methods by a policy alone so I created a service that checked the user was the owner of the model, and then extended the update and destroy methods in my controller.
api/services/isOwner.js:
/**
* Only allow access to models if they are the owner.
* Assumes an attribute called owners on the model and assumes it has a relationship that can be queried through the
* 'populate' waterline method.
*/
var actionUtil = require( 'sails/lib/hooks/blueprints/actionUtil' );
var _ = require( 'underscore' );
/**
* #param req
* #param res
* #param is {function} called if the user is the owner
* #param isnt {function} called if the user is not the owner. If not present will redirect 403 not authorised.
*/
module.exports = function isOwner( req, res, is, isnt ){
var ownerEmail = req.options.where.owner;
var Model = actionUtil.parseModel( req );
isnt = isnt || res.forbidden;
is = is || function(){
sails.log.warn( 'No callback defined for isOwner' );
res.ok();
};
Model.findOne({ id: req.params.id }).populate( 'owners' ).exec( function( error, model ){
var canEdit = _.find( model.owners, function( owner ){
return owner.email === ownerEmail;
});
canEdit ? is() : isnt();
});
};
api/controllers/AgencyController.js:
var update = require( 'sails/lib/hooks/blueprints/actions/update' );
var isOwner = require( '../services/isOwner' );
module.exports = {
/**
* Override the default blueprint update behaviour so only the owner can update a record.
*
* #param req
* #param res
*/
update: function( req, res ){
isOwner( req, res, update );
}
};
Doesn't feel like the nicest way of doing it but it's the only way I could think of. Just thought I'd share it on here just incase someone comes across the same problem OR someone has a better solution.
I am writing a package as part of a small application I am working on and one thing I need to do is fetch json data from an endpoint and populate it to a Server side collection.
I have been receiving error messages telling me I need to put by server side collection update function into a Fiber, or Meteor.bindEnvironment, or Meteor._callAsync.
I am puzzled, because there are no clear and concise explanations telling me what these do exactly, what they are, if and when they are being deprecated or whether or not their use is good practice.
Here is a look at what is important inside my package file
api.addFiles([
'_src/collections.js'
], 'server');
A bit of psuedo code:
1) Set up a list of Mongo.Collection items
2) Populate these using a function I have written called httpFetch() and run this for each collection, returning a resolved promise if the fetch was successful.
3) Call this httpFetch function inside an underscore each() loop, going through each collection I have, fetching the json data, and attempting to insert it to the Server side Mongo DB.
Collections.js looks like what is below. Wrapping the insert function in a Fiber seems to repress the error message but no data is being inserted to the DB.
/**
* Server side component makes requests to a remote
* endpoint to populate server side Mongo Collections.
*
* #class Server
* #static
*/
Server = {
Fiber: Npm.require('fibers'),
/**
* Collections to be populated with content
*
* #property Collections
* #type {Object}
*/
Collections: {
staticContent: new Mongo.Collection('staticContent'),
pages: new Mongo.Collection('pages'),
projects: new Mongo.Collection('projects'),
categories: new Mongo.Collection('categories'),
formations: new Mongo.Collection('formations')
},
/**
* Server side base url for making HTTP calls
*
* #property baseURL
* #type {String}
*/
baseURL: 'http://localhost:3000',
/**
* Function to update all server side collections
*
* #method updateCollections()
* #return {Object} - a resolved or rejected promise
*/
updateCollections: function() {
var deferred = Q.defer(),
self = this,
url = '',
collectionsUpdated = 0;
_.each(self.Collections, function(collection) {
// collection.remove();
url = self.baseURL + '/content/' + collection._name + '.json';
self.httpFetch(url).then(function(result) {
jsonData = EJSON.parse(result.data);
_.each(jsonData.items, function(item) {
console.log('inserting item with id ', item.id);
self.Fiber(function() {
collection.update({testID: "Some random data"}
});
});
deferred.resolve({
status: 'ok',
message: 'Collection updated from url: ' + url
});
}).fail(function(error) {
return deferred.reject({
status: 'error',
message: 'Could not update collection: ' + collection._name,
data: error
});
});
});
return deferred.promise;
},
/**
* Function to load an endpoint from a given url
*
* #method httpFetch()
* #param {String} url
* #return {Object} - A resolved promise if the data was
* received or a rejected promise.
*/
httpFetch: function(url) {
var deferred = Q.defer();
HTTP.call(
'GET',
url,
function(error, result) {
if(error) {
deferred.reject({
status: 'error',
data: error
});
}
else {
deferred.resolve({
status: 'ok',
data: result.content
});
}
}
);
return deferred.promise;
}
};
I am still really stuck on this problem, and from what I have tried before from reading other posts, I still can't seem to figure out the 'best practice' way of getting this working, or getting it working at all.
There are plenty of suggestions from 2011/2012 but I would be reluctant to use them, since Meteor is in constant flux and even a minor update can break quite a lot of things.
Thanks
Good news : the solution is actually much simpler than all the code you've written so far.
From what I've grasped, you wrote an httpFetch function which is using the asynchronous version of HTTP.get decorated with promises. Then you are trying to run your collection update in a new Fiber because async HTTP.get called introduced a callback continued by the use of promise then.
What you need to do in the first place is using the SYNCHRONOUS version of HTTP.get which is available on the server, this will allow you to write this type of code :
updateCollections:function(){
// we are inside a Meteor.method so this code is running inside its own Fiber
_.each(self.Collections, function(collection) {
var url=// whatever
// sync HTTP.get : we get the result right away (from a
// code writing perspective)
var result=HTTP.get(url);
// we got our result and we are still in the method Fiber : we can now
// safely call collection.update without the need to worry about Fiber stuff
});
You should read carefully the docs about the HTTP module : http://docs.meteor.com/#http_call
I now have this working. It appears the problem was with my httpFetch function returning a promise, which was giving rise to the error:
"Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment."
I changed this httpFetch function to run a callback when HTTP.get() had called with success or error.
Inside this callback is the code to parse the fetched data and insert it into my collections, and this is the crucial part that is now working.
Below is the amended Collections.js file with comments to explain everything.
Server = {
/**
* Collections to be populated with content
*
* #property Collections
* #type {Object}
*/
Collections: {
staticContent: new Mongo.Collection('staticContent'),
pages: new Mongo.Collection('pages'),
projects: new Mongo.Collection('projects'),
categories: new Mongo.Collection('categories'),
formations: new Mongo.Collection('formations')
},
/**
* Server side base url for making HTTP calls
*
* #property baseURL
* #type {String}
*/
baseURL: 'http://localhost:3000',
/**
* Function to update all server side collections
*
* #method updateCollections()
* #return {Object} - a resolved or rejected promise
*/
updateCollections: function() {
var deferred = Q.defer(),
self = this,
collectionsUpdated = 0;
/**
* Loop through each collection, fetching its data from the json
* endpoint.
*/
_.each(self.Collections, function(collection) {
/**
* Clear out old collection data
*/
collection.remove({});
/**
* URL endpoint containing json data. Note the name of the collection
* is also the name of the json file. They need to match.
*/
var url = self.baseURL + '/content/' + collection._name + '.json';
/**
* Make Meteor HTTP Get using the function below.
*/
self.httpFetch(url, function(err, res) {
if(err) {
/**
* Reject promise if there was an error
*/
deferred.reject({
status: 'error',
message: 'Error fetching content for url ' + url,
data: err
});
}
else {
/**
* Populate fetched data from json endpoint
*/
var jsonData = res.content;
data = EJSON.parse(res.content);
/**
* Pick out and insert each item into its collection
*/
_.each(data.items, function(item) {
collection.insert(item);
});
collectionsUpdated++;
}
if(collectionsUpdated === _.size(self.Collections)) {
/**
* When we have updated all collections, resovle the promise
*/
deferred.resolve({
status: 'ok',
message: 'All collections updated',
data: {
collections: self.Collections,
count: collectionsUpdated
}
});
}
});
});
/**
* Return the promise
*/
return deferred.promise;
},
/**
* Function to load an endpoint from a given url
*
* #method httpFetch()
* #param {String} url
* #param {Function} cb - Callback in the event of an error
* #return undefined
*/
httpFetch: function(url, cb) {
var res = HTTP.get(
url,
function(error, result) {
cb(error, result);
}
);
}
};