Include related model in an hasOne relation - loopbackjs

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

Related

How to get Count value in a HasMany relationship when querying sub Models?

On a Category model I created, I have the following relation:
"subscriptions":
{
"type": "hasMany",
"model": "Subscription"
"foreignKey": "",
"options": { "nestRemoting": true } } }
How could I get the Count() result when running:
this.userService.getCategories(this.currentUser.id,
{include: {relation: 'subscriptions', scope: {type: 'count'}}})
.subscribe((data: any[]) => { this.categories = data };
I would like to count the number of subscription when getting the categories belonging to the user, in the same observable().
Like show above, I tried with the scope of type 'count'. nothing comes.
Thanks for any help.
As an answer:
Loopback provides the 'include' filter to be able to retrieve related models.
As a consequence an Observable is retrieved. It does contain the relationship.
Getting the Count() is as simple as writing {{category.subscriptions.length}}in the HTML.
Hope this helps.

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();
}

Loopback - How to access custom method with related model?

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.

Can I use non-sequential id for loopback model?

Loopback uses sequential number for model ID. Can I use my own ID generator on server side? How do I go about doing that?
It is possible to specify Loopback generators (guid, uuid, ...) as a default function for id properties in your model definition file.
example with guid:
{
"name": "ModelName",
"base": "PersistedModel",
"idInjection": false,
"properties": {
"id": {
"type": "string",
"id": true,
"defaultFn": "guid"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
As far as I know, you can't specify there your own default function yet. See related github issue.
If you want more advanced behavior (e.g. your own generator), you can create models/model-name.js file and extend a constructor of your model.
Yes, you would need to do a few things:
Set "idInjection": false in the corresponding model.json to turn off automatic id injection
Add the property you want to your model, then set it to be an id either by setting "id": true on the property in the model.json, or selecting the id radial next to the prop in the composer
Generate and inject the id, probably with an operation hook on before save (https://docs.strongloop.com/display/public/LB/Operation+hooks) or maybe a mixin (https://docs.strongloop.com/display/public/LB/Defining+mixins)
If you use Loopback 4 then this is the setting for generating UUID in prime key.
Inside you Model change this.
#property({
type: 'string',
id: true,
defaultFn: 'uuidv4',
})
id?: string;
This is the way to gen a unique id in your table.

Why auto-migration doesn't create any properties in a Collection? How to make it to create them?

I have created models using slc loopback:model tool. Now I want Loopback to create corresponding MongoDB collections, that is to perform auto-migration.
One of the models is a Client model whose base class is a User model. That means that client/models/client.json is just empty because all its properties (fields) are inherited from User:
{
"name": "Client",
"plural": "Clients",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
So I think to myself that if I make an auto-migration, Loopback finds all User properties and creates Client collection with them. But it doesn't! My Client collection has only _id property.
Here is a my code for auto-migration:
module.exports = function(app) {
app.dataSources.mongodb.automigrate('Client', function(err) {
if (err) throw err;
});
};
My question:
Why Loopback doesn't use User model properties for my Client model? How to auto-migrate so that Loopback will create correct collection?
automigrate is used to migrate model data into tables i.e. Model name as tablename and Model's properties as table columns.
Now as you are using MongoDB, it drops and creates indexes as written in documentation. This is because MongoDB is schemaless.
So, probably you can avoid automigration and insert new documents directly.