Modify Strongloop/Loopback built-in input arguments in API Explorer - loopbackjs

I want to know if it is possible to change the default input parameters of builtin methods like 'create', but only for input (not using the hidden property) and only for this method. In this case, I want to suppress the "balance" parameter. In other words, in the pointed location, my api explorer needs to show the following:
{
"userId": "string"
}
I have managed change custom remote methods, like so:
Using the following code:
module.exports = function(User) {
User.makeDeposit = function(data, callback){
//Method logic
};
User.remoteMethod(
'makeDeposit',
{
http: {path: '/makedeposit', verb: 'post'},
returns: {type: User, default:'User', root: true},
accepts: {arg: 'req', type: 'object', default: prettyJSON(depositSchema), http: {source: 'body'}}
}
);
};
// Returns a pretty printed json
function prettyJSON(str){
return JSON.stringify(str, null, ' ');
}
// Input Schemas - Only used for API Explorer
var depositSchema = {};
depositSchema.userId = "hash123";
depositSchema.amount = 11.37;
But I cannot replicate for built-in methods. Any help?

Related

Nested resolvers with depth greater than 1

The Problem
Looking at this GraphQL query,
query {
asset {
name
interfaces {
created
ip_addresses {
value
network {
name
}
}
}
}
}
How do I define a resolver for just the network field on ip_addresses?
My First Thought
Reading docs the give examples of single nested queries, e.g
const resolverMap = {
Query: {
author(obj, args, context, info) {
return find(authors, { id: args.id });
},
},
Author: {
posts(author) {
return filter(posts, { authorId: author.id });
},
},
};
So I thought - why not just apply this pattern to nested properties?
const resolverMap = {
Query: {
asset,
},
Asset: {
interfaces: {
ip_addresses: {
network: () => console.log('network resolver called'),
},
},
},
};
But this does not work, when I run the query - I do not see the console log.
Further Testing
I wanted to make sure that a resolver will always be called if its on root level of the query return type.
My hypothesis:
Asset: {
properties: () => console.log('properties - will be called'), // This will get called
interfaces: {
created: () => console.log('created - wont be called'),
ip_addresses: {
network_id: () => console.log('network - wont be called'),
},
},
},
And sure enough my console showed
properties - will be called
The confusing part
But somehow apollo is still using default resolvers for created and ip_addresses, as I can see the returned data in playground.
Workaround
I can implement "monolith" resolvers as follows:
Asset: {
interfaces,
},
Where the interfaces resolver does something like this:
export const interfaces = ({ interfaces }) =>
interfaces.map(interfaceObj => ({ ...interfaceObj, ip_addresses: ip_addresses(interfaceObj) }));
export const ip_addresses = ({ ip_addresses }) =>
ip_addresses.map(ipAddressObj => ({
...ipAddressObj,
network: network(null, { id: ipAddressObj.network_id }),
}));
But I feel that this should be handled by default resolvers, as these custom resolvers aren't actually doing anything, but passing data down to another resolver.
The resolver map passed to the ApolloServer constructor is an object where each property is the name of a type in your schema. The value of this property is another object, wherein each property is a field for that type. Each of those properties then maps to a resolver function for that specified field.
You posted a query without posting your actual schema, so we don't know what any of your types are actually named, but assuming the network field is, for example, Network, your resolver map would need to look something like:
const resolver = {
// ... other types like Query, IPAddress, etc. as needed
Network: {
name: () => 'My network name'
}
}
You can, of course, introduce a resolver for any field in the schema. If the field returns an object type, you return a JavaScript Object and can let the default resolver logic handle resolving "deeper" fields:
const resolvers = {
IPAddress: {
network: () => {
return {
name: 'My network name',
}
}
}
}
Or...
const resolvers = {
Interface: {
ip_addresses: () => {
return [
{
value: 'Some value',
network: {
name: 'My network name',
},
},
]
}
}
}
Where you override the default resolver just depends at what point the data returned from your root-level field no longer matches your schema. For a more detailed explanation of the default resolver behavior, see this answer.

Loading Remote Data using Select2 and Webservice

I am using Select2 4.0.3 in my web forms .net application. I am trying to Load Remote data using a webservice, but its not working as expected.
First Issue am facing is that the webservice method is not getting called and am getting a console error:
System.InvalidOperationException: Missing parameter: text.
at System.Web.Services.Protocols.ValueCollectionParameterReader.Read(NameValueCollection collection)
at System.Web.Services.Protocols.HtmlFormParameterReader.Read(HttpRequest request)
at System.Web.Services.Protocols.HttpServerProtocol.ReadParameters()
at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()
So I tried removing the paremeter from the webservice call
<WebMethod()> _
Public Function GetDataFromService() As String
Doing this the method got fired, but still the items in the select2 did not get populated (screenshot atached).
Can someone help me to figure out where am I making a mistake.
Here are the details:
Select2 Code in the Webform:
$("#ddlEntity").select2({
ajax: {
url: "Service/InvoiceHTMLService.asmx/GetDataFromService",
type: 'POST',
delay: 250,
params: {
contentType: 'application/json; charset=utf-8'
},
dataType: 'json',
data: function (term, page) {
return {
text: term,
};
},
processResults: function (data, params) {
// parse the results into the format expected by Select2
// since we are using custom formatting functions we do not need to
// alter the remote JSON data, except to indicate that infinite
// scrolling can be used
params.page = params.page || 1;
return {
results: data.items,
pagination: {
more: (params.page * 30) < data.total_count
}
};
},
cache: true
},
escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
minimumInputLength: 1,
templateResult: formatRepo, // omitted for brevity, see the source of this page
templateSelection: formatRepoSelection // omitted for brevity, see the source of this page
});
WebService Method:
<WebMethod()> _
Public Function GetDataFromService(text As String) As String
Return "{['id':1, 'text':'Test1'],['id':2, 'text':'Test2']}"
End Function
Try this please
var fd = new FormData();
fd.append("text",term);
ajax: {
url: "Service/InvoiceHTMLService.asmx/GetDataFromService",
type: 'POST',
delay: 250,
params: {
contentType: 'application/json; charset=utf-8'
},
dataType: 'json',
data: fd
...
I think you did not create template for this select2. since you are using templateResult and templateSelection it requires template or you can remove those two options.
For your reference : https://select2.github.io/examples.html#templating
Updated:
they have changed query callback from
data: function (term, page) {
return {
text: term,
};
},
into
data: function (params) {
return {
q: params.term, // search term
page: params.page
};
},

Loopback Storage Component: Remote Method for hashing file and returning the corresponding sha256 string

On my loopback server, the loopback-storage-component is installed. My app use the angular-file-upload module. Both are working fine.
I need to generate a sha256 string of stored files (for example to arquive in the server, to send to the uploader, etc.).
How can I process my file to generate the hash
GET http:myloopbackserver/api/container/container1/hash/file.pdf
which responds with, for example
{
file: 'file.pdf',
sda256: 'bf94874852b8093545071e27808f7ef3b48668ffadbfcfbb7599562034dc7708'
}
Solved using Loopback Remote Methods, Streams and Crypto (from node api)
REST API
http://127.0.0.1:3000/api/containers/container1/hash/file.pdf
JSON RESPONSE
{"result":{"container":"container1","filename":"file.pdf","sha256":"29b887311cdce9f21be850e41e44393fa25c11e79cd20457acdb7cbad35ff6c1"}}
models/container.js
var crypto = require("crypto");
module.exports = function (Container) {
Container.hash = function (containerId, filename, cb) {
var fileInfo = {
container: containerId, filename: filename
}
var downloadedFileStream;
// Initializing crypto ReadStream using Loopback Storage Component API
downloadedFileStream = Container.downloadStream(containerId, filename, function (err) {
console.log(err);
cb(err, null);
});
// Initializing crypto writeStream, using crypto module from node
var hash = crypto.createHash('sha256');
hash.setEncoding('hex');
// When the Stream ends, send back the hashcode
downloadedFileStream.on('end' , function () {
hash.end();
fileInfo.sha256 = hash.read();
cb(null, fileInfo);
});
// Piping readStream to the WriteStream
downloadedFileStream.pipe(hash);
}
Container.remoteMethod('hash', {
accepts: [{arg: 'containerId', type: 'string', required: true}, {arg: 'filename', type: 'string', required: true},],
returns: {arg: 'result', type: 'string'},
http: {path: '/:containerId/hash/:filename', verb: 'get'}
});
};

loopback Add non static remote method error

I am trying to add a non-static remote method to a model. Just follow the code here. Unfortunately, I got some error message.
The following is my code
User.prototype.lastOrder = function(callback){
console.log('print this instance object: ', this);
callback(null)
};
User.remoteMethod('__get__lastOrder', {
isStatic: false,
accepts: [],
description: 'Get the latest order of the user',
http: {
path: '/lastOrder',
verb: 'get'
}
And when I invoke http://localhost:3000/v1/users/1/lastOrder. it gives me the following error:
The first argument to remoteMethod is the function name. What you have defined isn't valid. You need to define a function called, well, let's say lastOrder, and then modify your code like so:
User.prototype.lastOrder = function() {
}
User.remoteMethod('lastOrder', {
isStatic:false,
//more stuff here
}
User.prototype.lastOrder = function(callback){
console.log('print this instance object: ', this);
callback(null, "this is a test");
};
User.remoteMethod('lastOrder', { // should be lastOrder not __get__lastOrder
isStatic: false,
accepts: [],
description: 'Get the latest order of the user',
http: {
path: '/lastOrder',
verb: 'get',
status: 200
},
returns: {root: true, type: 'order'}
});

How do I add a non-remote method a model in Loopback?

How can I add a local method to a Model instance? In other words, a method on the model instance that will be executed by the server, and not exposed over the rest interface.
What I want to be able to do on the server is run:
Person.findById(1, (err, person) => {
let b = person.customFunction();
});
but I do not want customFunction exposed over the rest interface.
I know you can create a remote method on a model like this:
module.exports = function(Person){
Person.greet = function(msg, cb) {
cb(null, 'Greetings... ' + msg);
}
Person.remoteMethod(
'greet',
{
accepts: {arg: 'msg', type: 'string'},
returns: {arg: 'greeting', type: 'string'}
}
);
};
And I know if you leave off the call to Person.remoteMethod() you will have added a server-only method to Model class itself, but that isn't what I'm looking for.
This is quite easy, you just use Javascript's prototype functionality. In Person.js, just add this:
Person.prototype.customFunction = function() {
console.log('my custom code');
};
Then, give an person instance, you can call:
person.customFunction();