Loopback - How to access custom method with related model? - loopbackjs

First please apologize my english.
I'm trying to access a custom remote method of one of my model with a related model.
I've created 3 models in Loopback which are related this way:
User hasMany Shop & Shop belongTo User
Shop hasMany Credit & Credit belongTo Shop
User.nestRemoting("shops") for User to reach Credit through Shop, User being the main entry point for the API.
Credit can be bought by a User for a Shop to execute some actions which cost Credit.
I've also created a custom remote method for Credit: availableCredit(shopId) which sum the Credit being available (those bought by User for a Shopand those spent by a User for a Shop).
I've registered my custom method in credit.json and it went fine.
"methods": {
"availableCredits": {
"description": Returns the available credits for the shop",
"accessType": "READ",
"shared": true,
"http": {
"path": "/available",
"verb": "get",
"status": 200
},
"accepts": [
{
"arg": "shopId",
"type": "string",
"description": "shop id",
"required": true
}
],
"returns": {
"arg": "credits",
"type": "number"
}
}
}
Everything works BUT my custom method DOESN'T show on API Loopback Explorer under shops or user.
I do see and have access to all the build-in methods like (in user):
GET /users/{id}/shops/{nk}/credits
Or my custom method in credit:
GET /credits/available
but I DON'T have
GET /users/{id}/shops/{nk}/credits/available
nor
GET /shops/{id}/credits/available
Does anyone knows if it's possible and has any idea of how it works ?
Thanks in advance.

Related

How to get campaign ID from a real time facebook lead?

I'm using Facebooks Webhooks for lead generations. I successfully can fetch leadgen_id from the Facebooks callback.
So this is what Facebook returns for the leadgen field:
{
"object": "page",
"entry": [
{
"id": "0",
"time": 1583178814,
"changes": [
{
"field": "leadgen",
"value": {
"ad_id": "444444444",
"form_id": "444444444444",
"leadgen_id": "444444444444",
"created_time": 1583178812,
"page_id": "444444444444",
"adgroup_id": "44444444444"
}
}
]
}
]
}
Is it possible to somehow get campaign ID from these values?
I figure out how to get the campaign id from leadgenId. we can use the Facebook graph leadgen GET API
GET /v6.0/<leadgenid>?access_token=<ACCESS_TOKEN>&fields=campaign_id'
Host: graph.facebook.com
Hope it'll help someone in the future :)

Getting a limit response from Loopback, when no authentication is provided

I can't find a way to do it in the docs, and I have looked into as well here on Stack Overflow. I want to show a user a limited view of my JSON response from the API, before they have logged in.
So, as an example, I have a e-book I want to sell online. I want them only to see a preview link (epubFile.notAuthoried) of the book when not logged in, and the full link (epubFile.authorized) of the book when logged in. Both links are represented in the same table.
[
{
"title": "string",
"subTitle": "string",
"isPublished": true,
"publicationDate": "2017-10-20T11:07:31.258Z",
"epubFile": {
"notAuthorized": "filename-noauth.epub"
"authorized": "filename-auth.epub"
}
"id": "string",
"createdOn": "2017-10-20T11:07:31.258Z",
"updatedOn": "2017-10-20T11:07:31.258Z"
}
]
Is it even possible to filter out fields from the API Endpoints in loopback?
Or do I need to build a new custom API Endpoint?
first you'll have to set the permissions on your find and findById methods to $everyone so that both authorized and unauthorized users can call them
{
"name": "eBook",
"base": "PersistedModel",
[...]
"acls": [
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property":["find", "findById]
]
}
Next, you'll have to hook into the remote methods and modify the response depending on if hte user is logged in or not
const previewProperites = ['title', 'subTitle', etc...]
Ebook.afterRemote('find', (ctx, ebooks, next) => {
// pseudo code
if(!ctx.options.accessToken){
// no user logged in, only keep preview properties
ebooks.forEach(book => {
// get the properties of the book
var eBookProperties = Object.keys(book.__data);
eBookProperties.forEach(bookProp =>{
if(!previewProperties.some(pProp => pProp === bookProp)){
// ebook property not in preview list, so remove it
delete book.__data[bookProp]; // .__data is where loopback keeps its actual data
}
});
});
}
next();
}

Include related model in an hasOne relation

I have two models: Account and Customer both having an email address.
An Account can exist without a Customer and a Customer can exist without an Account.
However, an Account should return the related Customer record if this exists.
I was thinking about doing this by creating a hasOne relation on the Account using the unique identifier available in both records (the email address) as foreignKey.
Unfortunately this is not working.
These are my models:
Account
...
"properties": {
"username": {
"type": [
"string"
]
},
"email": {
"type": "string"
}
},
"validations": [],
"relations": {
"customer": {
"type": "hasOne",
"model": "Customer",
"foreignKey": "email"
}
}
...
Customer
...
"properties": {
"name": {
"type": [
"string"
]
},
"email": {
"type": "string"
}
},
"validations": [],
"relations": {}
...
By calling /api/account?filter={"include": ["customer"]} I don't get any additional information.
I don't understand if the problem is the foreignKey or the relation.
You could use an afterRemote hook to do the marshaling just before returning the requested instance.
However this won't be automatic, i.e. you still need to provide some sort of id to link the two instances together. In your case, if the email is such an id, then you would just search for a Customer instance with the same email as the Account instance.
The advantage is that you don't need to provide any extra filters or anything else to your query.
e.g.
Account.afterRemote('find', function(ctx, modelInstance, next) {
// Here you can check if the Account instance has a Customer instance
// via a regular find or findById, and if you do find the related instance
// you can add the data to ctx.result, which is the object that will be returned.
Customer.find({where:{email: modelInstance.email}}, addCustomerDetails);
function addCustomerDetails(err, linkedCustomer) {
// Add the Customer to the Account instance here
ctx.result.customer = linkedCustomer;
next();
}
});
And of course, you can do the same in the Customer afterRemote hook, but instead searching for the linked Account instance email.
Your models are defined well.
Be sure you have customer instance with existed email in db.
And the correct for of rest api calls is : /api/account?filter[include]=customer
UPDATE
Loopback overwrite the type of email because of the relation. hasOne relation should be setup over id foreign key not any other fields.
So if you want to fix the problem, you need to add below to properties section of account definition :
"id": false,
"email": {
"type": "string",
"id": true
}
The foreignKey field is just an alias for the relation. Having an email property and setting email as foreignKey does not create any sort of link between the two.
Then, it's simply a matter of using the REST API to instanciate the models, setup the relation and fetch the data
create an account
POST api/accounts/
{
"email": "account#bar.com"
}
create a related customer
POST api/accounts/1/foreignKey
{
"email": "customer#bar.com"
}
Fetch the accound and include the related customer
GET api/accounts/1?filter[include]=foreignKey

Ember 2.0 component request for data

I am trying to create an organisations-table component, which will display a list of organisations, and the user for which each organisation belongs. I pass the model for the organisations into the component via an organisations.hbs template, which results in the following response from the server:
{
"organisations": [
{
"id": 0,
"name": "Org0",
"user": 1
},
{
"id": 1,
"name": "Org1",
"user": 2
},
{
"id": 2,
"name": "Org2",
"user": 2
}
]
}
In order to display the username for each user, the component then makes its own call to the server querying against the id for each of the users.
Is this the correct approach? My understanding is that components are supposed to be isolated by design, only aware of the data passed into them, but in this example, the component is sending its own requests to the server for the additional data.
I have created this ember twiddle to hopefully give an idea of the structure of the app (comments welcome!).
Thanks in advance
The component itself has nothing to do with the calls, { async: true } means that the relationship won't be fetched unless it is "needed".
Needed in this case being the organisation.user.username in your component's template.
Keep in mind that model in your case is an array of DS.Model objects that have relationships.

Loopback 2.4: how to query certain fields of related model via REST API

I have User model over relational DB.
Each User can hasMany "users" where "chiefId" is FK.
"relations": {
"users": {
"type": "hasMany",
"model": "User",
"foreignKey": "chiefId"
},
}
I can query related users for each chief-user like this:
GET /users?filter={"include":"users"}
But it returns full user objects.
How should I query only "name" properties of related users?
Also is it possible to count related instances in one request to server?
A late reply but I just ran into this question now. It is possible:
filter: {
include:{
relation: "users",
scope: {
fields:["name"]
}
}
}
As far as I understood this question is about adding a nested filter on an include level, which seems to be not yet supported: https://groups.google.com/forum/#!msg/loopbackjs/T6onsYMJFOI/V4ILc3Obf3MJ
May be it's not the best way to approach this problem, but what you can do is a manual response transformation in .afterRemote('find', ...) hook.
/users?filter[fields][0]=name
See https://github.com/strongloop/loopback-example-relations-basic for more info.