How to override the default loopback model API paths - node.js

How can we override the default loopback REST API model end points? For example, I would like to invoke a custom model method named list when the following GET API is invoked.
I am referring to the documentation https://loopback.io/doc/en/lb2/Exposing-models-over-REST.html
1.API endpoint from loopback explorer: http://localhost:3000/api/Assets
2.Model method definition:
Asset.list = function(cb) {
console.log("called");
}
Asset.remoteMethod('list', {
http: {path: '/'},
returns: {type: 'Object', root: true}
});

If you want to use not default path (not used by default methods) you should add new remote method to JSON model config and define method in JS model file:
"methods": {
"myCustomMethod": {
"accepts": [
{
"arg": "req",
"type": "object",
"http": {
"source": "req"
}
}
],
"returns": [
{
"type": "Array",
"root": true
}
],
"http": {
"verb": "GET",
"path": "/myCustomPath"
}
}
}
Candidate.myCustomMethod = (req) => {//method code}
If you want to override default loopback path (autogenerated methods) you also should disable default method.
Candidate.disableRemoteMethodByName('find');
So now you can change in JSON config "/myCustomPath" to "/" and your remote method would refer to your function instead of default.

Your console.log("called"); should appear in your terminal only, not as a return on your web browser - that's maybe why you aren't seeing it at the moment.
If you want to see something on your web browser, you have to return a value for you callback, like:
module.exports = function (Asset) {
Asset.list = function(cb) {
console.log("called");
cb(false, {'called': true}); // cb(error, returned value(s));
}
Asset.remoteMethod('list', {
http: {verb: 'get'},
returns: {type: 'Object', root: true}
});
}
This file should be in your common/model/asset.js
In your server/model-config.json, do not forget to reference your model:
...
"Asset": {
"dataSource": "yourdatasource", //change this by your datasource name
"public": true
}
...

Related

How to update the only changed fields in Firestore?

In the users collection in Firestore, I have all users' uid as documents, inside each user document I am storing user preferences.
For example, here's a sample of user preferences I am saving in a specific user document:
{
"preferences": {
"settings": {
"themeColorMode": "light-mode",
"debugMode": false
},
"filterChips": {
"pathName": {
"filterChipsPreferences": true
}
}
}
}
I want to update user document with the data sent in the body of an API
I want that API to be compatible in a way such that
I should be able to add another root node other than preferences
I should be able to customize and add new nodes in preferences.settings & preferences.filterChips
I should be able to update a specific node - examples: preferences.settings.themeColorMode& preferences.filterChips.filterChipsPreferences
For example, in the request body I am sending this info:
{
"preferences": {
"settings": {
"themeColorMode": "dark-mode",
"isSoundNotificationOn": false,
"isAppListeningToStream": true
},
"filterChips": {
"pathName": {
"filterChipsPreferences": false
},
"FirstUsedSearch": "23/12/2021"
},
"columnDefs": {
"pathName": {
"ColumnDefsPreferences": true
}
},
},
"search": {
"savedSearches":["searchName"]
}
}
I am expecting this result to be saved in the user's document
{
"preferences": {
"settings": {
"themeColorMode": "dark-mode",
"isSoundNotificationOn": false,
"isAppListeningToStream": true,
"debugMode": false
},
"filterChips": {
"pathName": {
"filterChipsPreferences": false
},
"FirstUsedSearch": "23/12/2021"
},
"columnDefs": {
"pathName": {
"ColumnDefsPreferences": true
}
},
"search": {
"savedSearches":["searchName"]
}
}
}
How could I approach that?
Since you are using the Node.js Admin SDK on the server that calls Firestore, it is actually quite straightforward: you need to use the update() method of the DocumentReference.
The update() method accepts either an object with field paths encoded
as keys and field values encoded as values, or a variable number of
arguments that alternate between field paths and field values.
More precisely:
// 1/ Define your DocumentReference
const userId = ...;
const docRef = admin.firestore().doc(`users/${userId}`);
// 2/ Get the desired elements from the payload received by your API and use them to build the object to pass to the update method
// For example
const updateObj = {
preferences.settings.debugMode: false,
preferences.settings.themeColorMode: "myColor",
preferences.filterChips.filterChipsPreferences: "myPref",
aNewRootNode: {
foo: 'bar',
bar: 'foo'
}
}
await docRef.update(updateObj);
More info on how to update fields in nested objects can be found in the doc of the JS SDK.

uploading files - nodejs with typescript and tsoa and swagger

I build a web api with nodejs and express. I used tsoa to generate my routes and a swagger.json file.
I want to be able to upload files via the swagger landing page. I followed the instructions on this page: https://tsoa-community.github.io/docs/file-upload.html
My tsoa.json looks like this:
{
"entryFile": "src/app.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"controllerPathGlobs": [
"src/**/*.controller.ts"
],
"spec": {
"basePath" : "/",
"description": "",
"outputDirectory": "dist/api/public",
"specVersion": 3,
"specMerging": "recursive",
"paths": {
"/admin/commands/create": {
"post": {
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"in": "formData",
"name": "randomFileIsHere",
"required": true,
"type": "file"
}
]
}
}
}
},
"routes": {
"routesDir": "src/api"
}
}
My controller:
#Tags("Admin Commands")
#Route("admin/commands")
export class AdminCommandController extends Controller
{
#Post('create')
public async CreateCommand(#Request() request: express.Request): Promise<void>
{
try{
await this.handleFile(request);
}
catch {
throw new Error("Error uploading file.");
}
console.log("No error");
}
private handleFile(request: express.Request): Promise<any> {
const multerSingle = multer().single("randomFileIsHere");
return new Promise((resolve, reject) => {
multerSingle(request, undefined, async (error: Error) => {
if (error) {
reject("error");
}
resolve("file will be in request.randomFileIsHere");
});
});
}
}
but when I use the tsoa swagger command to generate the the swagger.json the multipart/form-data is not added to my swagger.json. If I manually add the multipart/form-data to my swagger.json the input field on my swagger landing page is a text field instead of a file field.
"paths": {
"/admin/commands/create": {
"post": {
"operationId": "CreateCommand",
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"in": "formData",
"name": "randomFileIsHere",
"required": true,
"type": "file"
}
],
"responses": {
"204": {
"description": "No content"
}
},
"tags": [
"Admin Commands"
],
"security": []
}
}
}
What am I missing or doing wrong? Furthermore, if this would work, how do i extract the file from the request. The documentation says the file will be added to the randomFileIsHere property of the request object, but if i call request.randomFileIsHere it will obviously throw an error.
I also tried to follow official documents. I was able to save the uploaded file, but codes didnt looked clean to me. Then I realised there is a #UploadedFile decorator. You can add it and read file directly.
#Post("profile-photo")
public async uploadProfilePhoto(#UploadedFile() profilePhoto) {
console.log(profilePhoto)
}
Using a HTTP testing tool ,like Postman, define a field named "profilePhoto" and select a file. You should be able to see it on server terminal.
I think that you can do something like that:
import fs from 'fs';
#Post("profile-photo")
public async uploadProfilePhoto(#UploadedFile('formInputNameForPhoto') profilePhoto) {
fs.writeFileSync('./photo', profilePhoto.buffer);
}
Here is the answer which helped me understand how can I save "ready" multer file object.

Elasticsearch create join field (Nodejs)

I have the following docs:
export class CustomerServiceResultType{
id: string;
body:{customerRelation: string};
}
export class CustomerType{
id: string;
body:{name: string};
}
I want CustomerServiceResultType to have a relation to CustomerType with the field: customerRelation.
this is my mapping:
await this.elasticsearchService.indices.putMapping({
"index": "db",
"type": "CustomerServiceResultType",
"body" : {
"properties": {
"customerRelation": {
"type": "join",
"relations": {
"CustomerServiceResultType": "CustomerType"
}
}
}
}
});
This is the error I get:
[Nest] 421512 - 11/21/2020, 6:40:42 PM [ExceptionsHandler] illegal_argument_exception +96414ms
ResponseError: illegal_argument_exception
There are no details about this error...
Thanks
There's nothing wrong with your request per-se -- I think it just requires one extra option: include_type_name: true.
It's undefined by default in nodejs but is required in ES 7.x on the server side. More reasoning behind this is here.
So this should do the trick:
await client.indices.putMapping({
include_type_name: true,
index: "db",
type: "CustomerServiceResultType",
body : {
properties: {
customerRelation: {
type: "join",
relations: {
CustomerServiceResultType: "CustomerType"
}
}
}
}
});
Typed indexes will be removed in 8.x so the best approach would actually be:
await client.indices.putMapping({
index: "db",
body : {
properties: {
customerRelation: {
type: "join",
relations: {
CustomerServiceResultType: "CustomerType"
}
}
}
}
});
BTW: your typescript types don't really play a role here because ES is a JSON-only interface and while there's the deprecated type aspect to ES, the two concepts are very distant.

Loopback - Return queried data as CSV file to browser

I'm querying a database table and want this dataset to be returned back to browser in CSV format i.e. in browser it should be like downloading file.
Following is my remote method which I've done so far. Don't know how to implement and what's missing.
Person.remoteMethod(
'exportPersons',
{
http: {path: '/exportPersons', verb: 'get'},
returns: [
{arg: 'Content-Type', type: 'application/octet-stream', http: { target: 'header' }}
]
}
);
Person.exportRoutes = function (cb) {
Person.find({}, function(err, data) {
var fields = ['id', 'name'];
// I'm using json2csv package
json2csv({ data: JSON.stringify(data), fields: fields }, function(err, csv) {
cb(null, csv);
});
});
}
I'm getting this error
TypeError: The header content contains invalid characters
Any kind help please !!!
This is how it works if you intended to return json:
//in model.json or when we register remote method
returns : [
{ arg: "first_property", "type" : "string"}, //first_property will be a key name
{ arg: "second_property", "type" : "string"}
]
//in remote method function when we run callback
cb( null, //error?
first_property_value,
second_property_value
)
And this is how it should be if you intended to return non-JSON result, and customize the response header:
//in model.json or when we register remote method
returns : [
{"type": "string", "root": true}, //doesnt need key name
{"arg": "Content-Type", "type": "string", "http": { "target": "header" }}, // assign the value later in the callback's third arguments
]
//in remote method function when we run callback
cb( null, //error?
string_data,
'application/octet-stream' // value for Content-Type header
)
note: loopback 3.x

I have created rest api using loopback and trying to call external api using rest connector and I want to display the result to the 1 api call

my case,I have create rest api which it should call external api and should display the response to the client using rest connector, i have added the below code in datasources.js
{"aci":{
"name": "aci",
"connector": "rest",
"operations": [{
"template": {
"method": "POST",
"url": "http://192.168.220.1:7800/esb/esb_cbs_service/AccountInfo",
"headers": {
"accepts": "application/json",
"content-type": "application/json"
},
"form": {
"ACCTINFOTRNRQ": {
"TRNUID": "12345",
"ACCTINFORQ": {
"DTACCTUP": "20050101"
}
}
},
"responsePath": "$.results[0].AccountInfoResponse.Item"
},
"functions": {
"acif": [
"customerId",
"customerName"
]
}
}]
}}
in model.js
module.exports = function(Acim) {
//remote method
Acim.extcall = function(Acim, cb){
cb(null, res);
}
};
Acim.remoteMethod('extcall',{
accepts: [{arg: 'customerId type" 'string''}]
returns: {arg: 'customerId', type: 'type'}
http: {path:'/getAci', verb: 'post'}
})
and when I reload the project I am facing this error. Please I am stuck here, what am I doing wrong?
C:\Users\user\acinfo>apic loopback:refresh
Updating swagger and product definitions
ERROR Cannot load the LoopBack application: Cannot load the LoopBack application: Cannot create data source "aci": Cannot initialize connector "RequestBuilder": Cannot read property 'root' of undefined
Please fix the problem and run apic loopback:refresh
Please fix the problem and run apic loopback:refresh

Resources