I want to define a remote method with the following path:
http://localhost:3000/api/dataSourceTestings/{id}/a
In the dataSourceTesting.json file I defined its path as :
"http": [
{
"path": "/{id}/a",
"verb": "put"
},
]
But when I send request on this end point it gives me the error that can't found the method for this endpoint.
Do I need to define a relationship for it or is there any other way to define a remote method for this path?
you should define your remotemethod in dataSourceTesting.js file:
DataSourceTesting.remoteMethod('putDataSourceTestings', {
accepts: [
{arg: 'id', type: 'string'}],
http: {path:'/:id/a', verb:'put'},
returns: {arg: 'result', type: 'json'}
});
then implement your putDataSourceTestings function:
DataSourceTesting.putDataSourceTestings = function(id, cb){
//your logic goes here
}
A blog related to this issue:
https://strongloop.com/strongblog/remote-methods-in-loopback-creating-custom-endpoints/
Related
My application is a bit large in size. It contains many feature modules and most are lazily loaded. I want to preload a lazily loaded module, which is included in forChild routes.
For this, I have referred to Angular documentation and followed their steps. I have provided a custom preloading strategy service mentioned below.
This is my custom preloading strategy file:
#Injectable()
export class CustomPreloadingWithDelayStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
return load();
} else {
return Observable.of(null);
}
}
}
app-routing file,
const routes: Routes =
[
XXX,
{
path: '',
data: {
base: true
},
component: MyComp,
children: [
{
path: 'page1/:id',
loadChildren: 'XXXXXXX'
},
{
path: 'page2',
loadChildren: 'XXXXXXXX'
},
{
path: 'page3',
loadChildren: 'app/feature-modules/folder1/my-folder1-module#Folder1Module'
}];
#NgModule({
imports: [RouterModule.forRoot(routes, {useHash: true, preloadingStrategy: CustomPreloadingWithDelayStrategy})],
exports: [RouterModule],
entryComponents: [ ]
})
export class AppRoutingModule {}
My Folder1Module's routing file:
const routes: Routes = [{
path: 'sub-page1/:data1/:data2',
loadChildren: 'app/feature-modules/sub-pages/pages/sub-page1.module#SubPage1Module'
}, {
path: 'sub-page2/:data1',
loadChildren: 'app/feature-modules/sub-pages/pages/sub-page2.module#SubPage2Module',
data: {preload: true}
}];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class Folder1RoutingModule {
}
So when I open this route /page3/sub-page1/data1/data2, SubPage2Module is to be preloaded. But that is not happening.
I have spent nearly 2 hours to understand why the module is not pre-loaded even when I was doing everything right.
The problem lies with children, any route that is defined and further distributed with children is considered as a normal route even if you've defined your modules within the children with the help of loadChildren. The preloadingStrategy just takes modules to preload and not path/routes. The moment you've defined your routes under children it considers it as normal route and moves on to scan other routes that are provided with loadChildren. Following is the interpretation of Angular with your routes:
const routes: Routes =
[
XXX, // Normal path
{
path: '',
data: {
base: true
},
component: MyComp,
children: [ // Normal path (no module) as children is used, move on
{
path: 'page1/:id',
loadChildren: 'XXXXXXX'
},
{
path: 'page2',
loadChildren: 'XXXXXXXX'
},
{
path: 'page3',
loadChildren: 'app/feature-modules/folder1/my-folder1-module#Folder1Module'
},
],
},
{ path: 'abc', loadChildren: '../path/to/module#AbcModule', data: { preload: true }} // Module found, preload it!
];
If you debug closely in your custom CustomPreloadingWithDelayStrategy then you will observe that your route /page3/sub-page1/data1/data2 can't even make up to the route parameter of preload() method because preloading is all about loading module and not about loading routes. However, our route abc does make an appearance! Hope it helps :)
In the context of a remote method, I'm trying to define a model schema of a parameter passed in the body. This object looks like this:
{
name: "Alex",
credentials: {
user: "alex",
pass: "pass"
}
}
So, I have this code in my remote method definition:
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {
"name": "string",
"credentials": {
"type": "object",
"properties": {
"user": "string",
"pass: "string"
}
}
}, http: {source: 'body'}, required: true
}
],
.....
Unfortunatelly, the details of this embedded object (credentials) are not shown in the generated Swagger explorer. This is what I see:
{
"user": "string",
"credentials": {}
}
I've tried many different ways but I could not show the properties of the credentials object.
Any ideas?
Loopback 2.x
Edit: Note the following only works for Loopback 2.x, as the type registry changed in 3.x.
The problem is that the data you are providing needs to be on the type property for the nested value. This should work:
MyModel.remoteMethod('postSomething', {
accepts: [
{
arg: 'person',
type: {
name: 'string',
credentials: {
type: {
user: 'string',
pass: 'string'
}
}
},
http: {
source: 'body'
},
required: true
}
],
//...
This also works with arrays:
accepts: [
{
arg: 'Book',
type: {
title: 'string',
author: 'string',
pages: [{
type: {
pageNo: 'number',
text: 'string'
}
}]
}
}
],
// ...
Loopback 3.x
Since the model registry and strong remoting changed in Loopback 3.x to only allow string or array types, you can't really avoid creating a new model. If you would like to quickly 'inline' a model without going through the full process of adding the model json file, adding it to model-config.json etc. you can register it directly on the app:
app.registry.createModel('Person', {
firstName: 'string',
lastName: 'string'
}, { base: 'Model' });
You can set the base to one of your other models if you want to extend an existing model (e.g, add another property that is only accepted in the given remote method)
If you want to create the model without cluttering up your model registry, you can do so by calling createModel on loobpack itself:
const loopback = require('loopback')
const modl = loopback.createModel({
name: 'Person',
base: null,
properties: {
firstName: {
type: 'string',
id: true // means it won't have an id property
}
}
});
In both of the above examples, you refer to the model by name to attach it to the remote method:
accepts: [
{
arg: 'Person',
type: 'Person'
}
],
// ...
Note you will need to create a sub-model for every sub-property (e.g. credentials)
Loopback swagger only picks up the outer object ignoring the properties of the object.
If you want to show a nested object in the swagger docs for the request body, you will have to make nested model.
Assuming you have a model called as person. You have to create another model named "credentials" having properties user and password. Then define the relationship in your person model's config
{
"name": "Person",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"credentials": {
"type": "embedsOne",
"model": "credentials",
"property": "credentials",
"options": {
"validate": true,
"forceId": false
}
}
},
"acls": [],
"methods": {}
}
And add a reference to this model where you define your remote method
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {Person},
http: {source: 'body'}, required: true
}
],
To avoid "Treating unknown remoting type" warning make sure your model is marked as "public" inside your "model-config.json"
Using loopback, I have created a connection to an existing API using the REST connector, which is working well. I would however like to pass through the oAuth token coming from the client.
I can get hold of the oAuth token by grabbing ctx.req.headers.authorization from the Model.beforeRemote method, but can't seem to figure out a way of passing it to the REST connector as a new header.
I've tried a couple of things:
Adding a hook using Model.observe (but this doesn't seem to fire with the REST connector).
Using a template with an authorization field - but have not been able to get this working correctly.
Any ideas appreciated.
With the connector below you should be able to pass the OAuth token into the function (as first parameter in the example). Does something like this not work for you?
{
connector: 'rest',
debug: false,
options: {
"headers": {
"accept": "application/json",
"content-type": "application/json",
"authorization": "{oauth}"
},
strictSSL: false,
},
operations: [
{
template: {
"method": "GET",
"url": "http://maps.googleapis.com/maps/api/geocode/{format=json}",
"query": {
"address": "{street},{city},{zipcode}",
"sensor": "{sensor=false}"
},
"options": {
"strictSSL": true,
"useQuerystring": true
},
"responsePath": "$.results[0].geometry.location"
},
functions: {
"geocode": ["oauth", "street", "city", "zipcode"]
}
}
]}
Wanted to answer this, and build on Bryan's comments. Firstly, in datasources.json, you'll want to setup the REST connector:
{
"name": "connect",
"connector": "rest",
"debug": "true",
"operations": [
{
"template": {
"method": "GET",
"url": "http://server/api",
"headers":{
"authorization": "Bearer {token}"
}
},
"functions": {
"get": ["token"]
}
}
]
}
As Bryan covered, it possible to put the auth header in each call, or at the root of the connector.
Secondly, and this is the bit I was stuck on, in order to pass the token to the API call from a model, it's required to generate a remote method that passes the token as a query parameter. This is what it looks like in this example:
module.exports = function (Model) {
Model.disableRemoteMethod('invoke', true);
Model.disableRemoteMethod('get', true);
Model.call = function (req, cb) {
var token = req.token;
Model.get(token, function (err, result) {
cb(null, result);
});
};
Model.remoteMethod(
'call',
{
http: {path: '/', verb: 'get'},
accepts: [
{arg: 'req', type: 'object', http: {source: 'req'}}
],
returns: {
root: true
}
}
);
};
Notice how the req argument is required in order to provide the request to the model. You also notice that I've disabled the original get and invoke methods (replacing it with a more REST-friendly resource).
Finally, you'll need to get the token into the request. For this, it's easy enough to use some middleware. Here's an example from server.js:
app.use('/api', function (req, res, next) {
oidc.authenticate(req, function (err, token) {
if (err) {
return res.send({status: 401, message: err});
}
req.token = token;
next();
});
});
In the above example, I'm using an internal OIDC provider to validate the token, but of course, you can use anything.
I'm having problems with the Grape gem and the parameters validation.
The idea behind this is to create a complex entity using nested attributes through an API service.
I have a method to create a trip, trip have many destinations and i want to pass that destinations using a hash (using the accepts_nested_attributes_for helper).
I have this grape restriction over the parameter:
requires :destinations, type: Hash
And I'm trying to send something like this:
{ destinations => [
{ destination: { name => 'dest1'} },
{ destination: { name => 'dest2'} },
{ destination: { name => 'dest3'} }
]}
In order to build something like the structure below inside the method and get the trip created:
{ trip: {
name: 'Trip1', destinations_attributes: [
{ name: 'dest1' },
{ name: 'dest2' },
{ name: 'dest3' }
]
}}
I'm using POSTMAN chrome extension to call the API method.
Here's a screen capture:
If someone can help me i would be very grateful.
By the looks of what you are trying to send, you need to change the Grape restriction, because destinations is an Array, not a Hash:
requires :destinations, type: Array
You don't need the "destination" hash when sending the request:
{ destinations => [
{ name => 'dest1', other_attribute: 'value', etc... },
{ name => 'dest2', other_attribute: 'value', etc... },
{ name => 'dest3', other_attribute: 'value', etc... }
]}
This creates an Array of hashes.
In order to send this through POSTMAN, you'll need to modify that destinations param your sending and add multiple lines in POSTMAN. Something like:
destinations[][name] 'dest1'
destinations[][other_attribute] 'value1'
destinations[][name] 'dest2'
destinations[][other_attribute] 'value2'
destinations[][name] 'dest3'
destinations[][other_attribute] 'value3'
Hope this answers your questions. Let me know if this is what you were looking for.
When I send data ( store in codeblock ) to my laravel 4 server I get "method not allowed" and the server returns all methods allowed except POST. When I comment out 'id' in my model, everything works. ( don't want to comment out id)
I tried the writeRecordId:false and writeAllFields:false in my writer property but this doesn't remove the id while sending..
Ext.define('Equipment.store.Equipments', {
extend: 'Ext.data.Store',
model: 'Equipment.model.Equipment',
requires: ['Ext.data.proxy.Rest'],
alias: 'store.Equipments',
proxy: {
type: 'rest',
url: '/json/stock/equipment',
reader: {
type: 'json',
root: 'data',
successProperty: 'success'
},
writer: {
type: 'json'
}
},
groupField: 'location'
});
data send:
{"id":0, "location":"Building123","locationDetails":"office 2","locationIndex":"drawre 5", "description":"item 7"}
I guess I've sorta solved it I think:
Ext.define('Equipment.model.Equipment', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id',
type: 'number',
useNull: true
},
Placing 'useNull: true' along id sets {"id":null, ... in the data which is accepted by the server. Anyone care to comment?