Make index unique of a model in loopback - node.js

This is what my model looks like
"name": "mClass",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"mClassName": {
"type": "string",
"required": true,
"index": {
"unique": true
}
},
"mClassUrl": {
"type": "string"
},
"mCreatedBy": {
"type": "string",
"required": true
},
"mCreatedAt": {
"type": "date",
"required": true,
"default": "$now"
},
"mUpdatedAt": {
"type": "date"
},
"mDeletedAt": {
"type": "date"
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "DENY"
}
],
"methods": {}
}
i want to make class name as unique. I tried index:{unique:true} but it seems that it is not working as i can still create classes with same name. Please help me on what changes to be made.

In case somebody is looking for the Loopback v4 solution to this problem. In Loopback v4 you amend the #property annotation with "index: {unqiue: true}":
#model()
export class Client extends Entity {
#property({
type: 'string',
required: true,
index: {
unique: true,
},
})
name: string;
}
Then you must update your schema:
npm run migrate
or recreate it:
npm run migrate -- --rebuild
PostgresQL is support and MySQL, too, I guess

Alternatively, in your mClass.js you can do the following:
module.exports = function (mClass) {
mClass.validatesUniquenessOf('mClassName');
};

What you need to do is to define index in your model.json:
"indexes": {
"indexName": {
"keys": {
"mClassName": 1
},
"options": {
"unique": true
}
}
}
Then you need to run autoupdate on your datasource when the server starts, great place for that would be a boot script, eg. ./server/boot/db-autoupdate.js:
'use strict';
module.exports = async function(app) {
// this will trigger the database structure update, like creating indexes (which is not handled by auto-migrate module
await app.dataSources.db.autoupdate();
};

From the docs:
id No Boolean Whether the property is a unique identifier. Default is false. See Id property below.
So I'd assume that instead of index: {...}, you have to use id: true.

Related

Loopback public access by model values

Is there any way to have hook which is checkin that has model attribute public and is it true? If it's true, access token is not required? At the moment I have implemented custom endpoints. But is there some other ways?
I have model where is public attribute, like this:
{
"name": "Model",
"plural": "model",
"base": "PersistedModel",
"idInjection": false,
"options": {
"validateUpsert": true
},
"properties": {
"uuid": {
"type": "string",
"defaultFn": "uuid",
"id": true
},
"orderNumber":{
"type":"number"
},
"public":{
"type":"boolean",
},
"roles": {
"type": "object",
"dataType":"longtext"
},
"groupId": {
"type": "string"
},
"created": {
"type": "date",
"required": true,
"defaultFn": "now"
},
"updated": {
"type": "date",
"required": true,
"defaultFn": "now"
}
},
"validations": [],
"relations": {
},
"acls": [{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "DENY"
}],
"methods": {},
"scope": {
},
"mixins": {
}
}
You could achieve that by creating a dynamic role called accessiblePublic for example.
Next, in the ACL I would define that this role has access to the endpoint.
Then I would resolve that role dependently on the model that is in the context.
I recommend to read about dynamic roles (and the example there) in the Docs: Dynamic Roles

Loopback - how to get id of user and insert into related model? (hasMany)

What I am trying to figure out is how to get the id of the current authenticated user and use that when creating records in the DB as a foreign key of a different model?
To be more specific I need to get the id of the current authenticated user (model: CommonUser) and use that id as a FK when creating a new Event.
The relationships:
I have created a Model based on the User model called CommonUser. Common user has many Events. Event belongs to Common User.
So Event has a foreignKey called commonUserId.
How do I get the id of the user and use that when doing the insert?
I would have thought this would be automatic as part of the process as far as setting up relationships is concerned? Is that incorrect?
Also to complicate matters I have an Event Look-Up table (i will worry about this next so don't feel obligated to dive to deep) because Event also hasAndBelongsToMany through Event Lookup.
User
{
"name": "CommonUser",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"events": {
"type": "hasMany",
"model": "Event",
"foreignKey": "eventId",
"through": "EventLookUp"
},
"friends": {
"type": "hasMany",
"model": "CommonUser",
"through": "Friend",
"foreignKey": "friendId"
}
},
"acls": [],
"methods": {}
}
Event
{
"name": "Event",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string"
},
"radius": {
"type": "number",
"default": 50
},
"status": {
"type": "number"
},
"location": {
"type": "geopoint",
"required": true
}
},
"validations": [],
"relations": {
"owner": {
"type": "belongsTo",
"model": "CommonUser",
"foreignKey": "commonUserId"
},
"commonUsers": {
"type": "hasAndBelongsToMany",
"model": "CommonUser",
"foreignKey": "ownerId",
"through": "EventLookUp"
},
"galleries": {
"type": "hasOne",
"model": "Gallery",
"foreignKey": ""
},
"photos": {
"type": "hasMany",
"model": "Photo",
"foreignKey": "",
"through": "Gallery"
}
},
"acls": [],
"methods": {}
}
Event Lookup
{
"name": "EventLookUp",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
If I can be pointed in the right direction that would be fantastic. It's hard to find an answer reading through the documentation. I think I need to use an operation hook before insert and set the Event Models properties? What is loopback's best practice as far as this goes?
In loopback swagger when you login as a user using loopback's default users/login api , you get access token object as response.You can copy the id of access token and paste into the box in top right corner in swagger and set the access token.Thus internally your accesstoken is set in loopback and for your every request from swagger, loopback append the access token along with the request.In this way you can get the access token from ctx(context) in remote methods.
For create, findOrCreate, save an event obj:
Event.observe('before save', function updateUserId(ctx, next) {
let userId = ctx.options.accessToken.userId;`
if (ctx.instance) {
ctx.instance.commonUserId = userId;
}
next();
});
For updateAttributes for event obj:
Event.observe('before save', function updateUserId(ctx, next) {
let userId = ctx.options.accessToken.userId;
if (ctx.currentInstance) {
ctx.currentInstance.commonUserId = userId;
}
next();
});
I found a solution but I am unsure if it is best practice or if there is a better way to handle this.
Anyways I created an Operation Hook inside "/common/models/event.js" and appearently you can get access to the context within this hook.
If there is a better way to do this I would like to know.
'use strict';
module.exports = function (Event) {
Event.observe('before save', function updateForeignKeyCommonUserId(ctx, next) {
// console.log(ctx.options);
/* WHAT Options Looks Like */
// { accessToken:
// { id: 'Z20R1RsnumWEdDzR3TyCCbmZ0DTp4tOh2cviU6JGsrlNIYCs3KchQ7mdAnhTc1VQ',
// ttl: 1209600,
// created: 2018-06-26T17:41:28.298Z,
// userId: 3 },
// authorizedRoles: {} }
// console.log("THE USER ID IS: ");
// console.log(ctx.options.accessToken.userId); // Current User Id
var userId = ctx.options.accessToken.userId;
// So appearently "the context provides either an instance property or a pair of data and where properties"
// #todo: find out why there can be a 'instance' or 'data'.
if (ctx.instance) {
ctx.instance.commonUserId = userId; // instance
} else {
ctx.data.commonUserId = userId; // data
}
next();
});
};
If someone wouldn't mind explaining where context comes from and how it gets populated that would be awesome!

loopback model json array of objects strict filter

I'm using strongloop loopback v3 REST API with mongoDB as datasource. My model order.json is
{
"name": "order",
"base": "PersistedModel",
"strict": true,
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"orderNo": {
"type": "string"
},
"lines": {
"type": [
{
"type": {
"description": "string",
"quantity": "number"
}
}
]
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
I set "strict": true so that the model accepts only predefined properties. But this does not work for properties in the array lines.
I.E. if you post this object to the API you get an ValidationError (Code 422) as expected:
{
"orderNo": "xyz",
"someOtherProp": "hello",
"lines": [
{
"description": "abc",
"quantity": 5
}
]
}
But if you post this JSON object loopback saves the object to mongoDB
{
"orderNo": "xyz",
"lines": [
{
"description": "abc",
"quantity": 5,
"someOtherProp": "hello"
}
]
}
My question is about if there are any flags to be set in the model JSON to validate an array of objects? Or do I have to validate the nested documents by my own via the order.js model extension file?
define the lines as another model and make it relation with type embedsMany in order model.
lines model
{
"name": "line",
"base": "Model",
"strict": true,
"idInjection": true,
"properties": {
"description": {
"type": "string"
},
"quantity":{
"type":"number"
}
}
}
order model
{
"name": "order",
"base": "PersistedModel",
"strict": true,
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"orderNo": {
"type": "string"
}
},
"validations": [],
"relations": {
"lines":{
"type": "embedsMany",
"model": "line",
"property": "lines"
}
},
"acls": [],
"methods": {}
}
this way loopback will validate line model

Loopback join/include two collections

I would like to include my product_product model inside product_template.
1 - Each product template has its own product_product variations "HasMany" .
2 - product_product has only one template "BelongsTo" product_template
3- product_template should be filled with only related product_product variations.
4- The two models are saved seprately, so when I call for find() function I would like to get a product_template model filled with the product_product related to it (Could be more than one)
Get product template function :
Producttemplate.find({
include: {
relation: 'variations',
scope: {
fields: ['sku', 'name', 'price', 'regular_price', 'weight', 'description', 'stock_quantity'],
},
},
})
product_product Model :
This model should be included in the product_template
{
"name": "product_product",
"base": "PersistedModel",
"strict": true,
"options": {
"validateUpsert": true
},
"properties": {
"_id_Odoo": {
"type": "number"
},
"sku": {
"type": "string",
"id": true,
"required": true,
"description": "Yes it's SKU"
},
#fields
},
"validations": [],
"relations": {
"product": {
"type": "belongsTo",
"model": "product_template",
"foreignKey": "_id_Odoo"
}
},
"acls": [],
"methods": {}
}
product_template Model :
This model should include the product_product
{
"name": "product_template",
"base": "PersistedModel",
"strict": true,
"options": {
"validateUpsert": true
},
"properties": {
"_id_Odoo": {
"type": [
"number"
]
}
"sku": {
"type": "string",
"id": true,
"required": true,
"description": "Yes it's SKU"
},
"name": {
"type": "string"
}
},
"scope": {
"include": "variations"
},
"hidden": ["_id_Odoo"],
"validations": [],
"relations": {
"variations": {
"type": "hasMany",
"model": "product_product",
"foreignKey": "_id_Odoo"
}
},
"acls": [],
"methods": {}
}
Result :
This the result of get product template above :
{ sku: 'AHWLI05942-FUSCHIA', variations: List [] },
{ sku: 'AHWLI05943-BLACK', variations: List [] },
{ sku: 'AHWLI05943-BURGUNDY', variations: List [] },
{ sku: 'AHWLI05944-BLACK', variations: List [] },
{ sku: 'AHWLI05944-MARRON', variations: List [] },
{ sku: 'AHWLI05945-BLUE', variations: List [] }
When I point into variations i get a function and into variations.list i get undefined any ideas how to get exact structure ?
example code part of my model "TeamRole" which belongsTo "Team" and User" in model level.
teamRole.json
"validations": [],
"relations": {
"team": {
"type": "belongsTo",
"model": "Team",
"foreignKey": ""
},
"user": {
"type": "belongsTo",
"model": "User",
"foreignKey": ""
}
}
Example search query for team role,
query.js
app.models.TeamRole.find({
where: {
userId: user.id
},
include: {
relation: 'team'
}
},function(err,teams){
console.log("teams will have all the include teams[] with team role ")
});
Hope using above example will help you.
sorry for late response, it was just misunderstanding of the relation and structure, i made a function that verifies if the product model exists in the template if yes, i push the template id in the product model and it worked fine.
product_product Model : I deleted the relation in the model below because it's useless, so i removed id property in sku and removed _id_Odoo field, then i added id field and parent_template field that contains the template model id.
{
"name": "product_product",
"base": "PersistedModel",
"strict": true,
"options": {
"validateUpsert": true
},
"properties": {
"id": {
"type": "number"
"id": true
}
"parent_template": {
"type": "number"
},
"sku": {
"type": "string",
"required": true,
"description": "Yes it's SKU"
},
#fields
}
product_template Model : I removed the _id_odoo and id property in sku and created an id for this model and in relation i made parent_template as foreignkey
{
"name": "product_template",
"base": "PersistedModel",
"strict": true,
"options": {
"validateUpsert": true
},
"properties": {
"id": {
"type": "number",
"id" : true
}
"sku": {
"type": "string",
"required": true,
"description": "Yes it's SKU"
},
"name": {
"type": "string"
}
},
"scope": {
"include": "variations"
},
##########
"relations": {
"variations": {
"type": "hasMany",
"model": "product_product",
"foreignKey": "parent_template"
}
},
#############
}

Nested (embedded) tree model in loopback with mongodb

I try to figure out how to set a very simple nested "treenode" model in loopback with mongodb. The idea is there will be only one model (for this): treenode which can contain other treenodes. I would like to store them at once via mongodb nested documents:
- TreeNode (document):
Name: "A",
Nodes: [
{
Name: "A-A",
Nodes: [
{
Name: "A-A-A",
Nodes: []
},
{
Name: "A-A-B",
Nodes: []
},
{
Name: "A-A-C",
Nodes: []
}
},
{
Name: "A-B",
Nodes: []
},
]
Additionally each node at any level has relations to other models.
There will be many top-level root treenodes (documents). Which relation type and how should I use for this?
Unfortunately, there isn't much documentation on this topic yet. For now, see http://docs.strongloop.com/display/LB/Embedded+models+and+relations
You should define the nested models separately and then declare them as transient models. Then loopback should store them in its parent model, as explained http://loopback.io/doc/en/lb2/Embedded-models-and-relations.html#transient-versus-persistent-for-the-embedded-model
Define a transient data source
server/datasources.json
{
...
"transient": {
"name": "transient",
"connector": "transient"
}
}
server/model-config.json
{
...
"BaseNode": {
"dataSource": "db",
"public": true
},
"NestedNode": {
"dataSource": "transient",
"public": false
}
}
And the model definitions should be somthing like this:
common/models/NestedNode.json
{
"name": "NestedNode",
"base": "Model",
"properties": {
"name": {
"type": "string"
}
},
"relations": {
"nodes": {
"type": "referencesMany",
"model": "NestedNode",
"options": {
"validate": true,
"forceId": false
}
}
}
common/models/BaseNode.json
{
"name": "BaseNode",
"base": "PersistedModel",
"properties": {
"name": {
"type": "string"
}
},
...
"relations": {
"nestedNode": {
"type": "embedsMany",
"model": "Link",
"scope": {
"include": "linked"
}
}
},
...
}
You also may experience curcular reference problems.

Resources