Extend model with count of related model - node.js

I'm trying to build a simple blog with loopback. I want to extend get Posts with the amount of comments.
I have two possible ways in my mind.
1) Extend the response of the get-posts by a count of the comments, this would be my favorite way, but I have no idea how to extend the reposne.
2) I have tried to observe the comment saving and to get the posts-model, but I can't change it.
post.json
{
"name": "post",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "string",
"required": true
}
"published": {
"type": "boolean",
"required": true,
"default": false
}
"commentCount": {
"type": "number",
"default": 0
}
},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": ""
},
"comments": {
"type": "hasMany",
"model": "comment",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": [
"__create__comments",
"__get__comments"
]
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__comments"
}
],
"methods": {}
}
comment.json
{
"name": "comment",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"content": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": ""
},
"idea": {
"type": "belongsTo",
"model": "post",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
"methods": {}
}
comment.js ##
var loopback = require('loopback');
module.exports = function(Comment) {
Comment.observe('after save', function(ctx, userInstance, next) {
var postId = ctx.instance.postId;
// loopback.getModel('post').definition.rawProperties.commentCount... something... something...
});
};
I'm still very new to loopback and I don't know what is the best way to achieve the solution. Maybe you have a third, better way? Or maybe anyone can help me to complete the comment.js.

Fisrt, in your comment.json, you've written idea instead of post:
"post": { //change here
"type": "belongsTo",
"model": "post",
"foreignKey": ""
}
Secondly, you simply add one commentCount in the post linked to your comment in your after save method and then update the attributes of your post:
'use strict';
var app = require('../../server/server');
var models = app.models;
var Post;
// pattern to get your models on start event
app.on('started', function () {
Post = models.post;
});
module.exports = function(Comment) {
Comment.observe('after save', function(ctx, next) {
// only add a commentCount if it's a new instance
if (ctx.instance && ctx.isNewInstance && ctx.instance.postId) {
Post.findOne({where: {id: ctx.instance.postId}}, function (err, post) {
if (!err) {
post.updateAttributes({commentCount: post.commentCount++});
}
});
}
next();
});
};
Another solution would be to create a customGet endpoint in your post.js file:
'use strict';
module.exports = function(Post) {
Post.customGet = function (postId, cb) {
Post.findOne({where: {id: postId}, include: 'comments'}, function (err, post) {
if(err){
return cb(err, {});
}
post.commentCount = post.comments.length;
return cb(err, post);
});
}
Post.remoteMethod('customGet', {
description: 'New endpoint with the commentCount',
accepts: {arg: 'postId', type: 'string'},
returns: {arg: 'post', type: 'object'},
http: {verb: 'get'}
});
};
You can improve this method a bit but you get the idea.

Related

Loopback v3 add column to Table -> "Unknown column 'column_name' in 'field list'"

I have a webshop based on React with a Loopback rest api. Now i want to add new columns to the client Table. If i add the object as property option to the client.js the error shows "Unknown column 'gender_delivery' in 'field list'". See the image below. The comments are not included in the saved code.
{
"name": "client",
"plural": "clients",
"base": "User",
"idInjection": true,
"options": {
"allowEternalTokens": true,
"validateUpsert": true
},
"ttl": 43200,
"mixins": {
"DisableAllMethods": {
"hide": [
"prototype.__create__orders",
"prototype.__delete__orders",
"prototype.__destroyById__orders",
"prototype.__updateById__orders",
"prototype.__count__cart",
"prototype.__create__cart",
"prototype.__destroy__cart",
"prototype.__update__cart",
"prototype.__destroyById__cart",
"prototype.__findById__cart",
"prototype.__addArticle__cart",
"prototype.__updateById___cart",
"prototype.__addArticles__cart",
"reset"
]
},
"TimeStamp": {
"createdAt": "created_at",
"updatedAt": "updated_at",
"required": false,
"validateUpsert": true,
"silenceWarnings": false
}
},
"restrictResetPasswordTokenScope": true,
"emailVerificationRequired": true,
"hidden": [
"password",
"verificationToken",
"securityQuestion",
"securityQuestion",
"verificationHash"
],
"properties": {
"id": {
"type": "number",
"id": true,
"generated": true,
"mysql": {
"columnName": "id",
"dataType": "int",
"dataLength": null,
"dataPrecision": 11,
"dataScale": 0,
"nullable": "N"
}
},
"oid": {
"type": "number"
},
"username": {
"type": "string",
"required": true
},
"password": {
"type": "string",
"required": true
},
"disable": {
"type": "boolean",
"required": true,
"default": false
},
"name": {
"type": "string"
},
"first_name": {
"type": "string",
"required": true
},
"middle_name": {
"type": "string"
},
"last_name": {
"type": "string",
"required": true
},
"street": {
"type": "string",
"required": true
},
"city": {
"type": "string",
"required": true
},
"telephone": {
"type": "string"
},
"fax": {
"type": "string"
},
"email": {
"type": "string",
"required": true
},
"deleted": {
"type": "boolean",
"required": true,
"default": false
},
"title": {
"type": "string"
},
"zip": {
"type": "string",
"required": true
},
"country": {
"type": "string",
"required": true
},
"company": {
"type": "string"
},
"lastlogin": {
"type": "number"
},
"is_online": {
"type": "boolean",
"required": true,
"default": false
},
"password_orig": {
"type": "string"
},
"gender": {
"type": "number"
},
"telephone2": {
"type": "string"
},
"iscompany": {
"type": "number"
},
"companyid": {
"type": "string"
},
"houseno": {
"type": "string"
},
"addressadditional": {
"type": "string"
},
"tax": {
"type": "boolean",
"required": true,
"default": true
},
"tax_type": {
"type": "number"
},
"securityQuestion": {
"type": "string",
"required": true
},
"securityAnswer": {
"type": "string",
"required": true
},
"verificationHash": {
"type": "string"
},
"showTutorial": {
"type": "boolean",
"default": true
},
// NEW COLUMNS
"gender_delivery": {
"type": "number"
},
"first_name_delivery": {
"type": "string",
"required": true
},
"last_name_delivery": {
"type": "string",
"required": true
},
"addressadditional_delivery": {
"type": "string"
},
"street_delivery": {
"type": "string",
"required": true
},
"houseno_delivery": {
"type": "string"
},
"zip_delivery": {
"type": "string",
"required": true
},
"city_delivery": {
"type": "string",
"required": true
},
"country_delivery": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"cart": {
"type": "hasOne",
"model": "cart",
"foreignKey": "clientId",
"primaryKey": "id"
},
"addresses": {
"type": "hasMany",
"model": "address",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
},
"orders": {
"type": "hasMany",
"model": "order",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
},
"projects": {
"type": "hasMany",
"model": "project",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
}
},
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "create"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "deleteById"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "login"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "logout"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "findById"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "patchAttributes"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "replaceById"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "verify"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "verifyAccount"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "clearPassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "changePasswordPublic"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "confirm"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "resetPassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "changePassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "setPassword"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "deleteById"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "exists"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "findById"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "upsert"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "updateAll"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateClientAttributes"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__accessTokens"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__addArticle__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById___cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__addArticles__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__createPdf__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__projects"
}
],
"methods": {}
}
The weird thing is that whenever i do the same with another Table it works perfectly fine. See example generas.
{
"name": "genera",
"plural": "generas",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"mixins": {
"IgnoreDeleted": true,
"RowCounter": true,
"TimeStamp": {
"createdAt": "created_at",
"updatedAt": "updated_at",
"required": false,
"validateUpsert": true,
"silenceWarnings": false
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"generated": true,
"mysql": {
"columnName": "id",
"dataType": "int",
"dataLength": null,
"dataPrecision": 11,
"dataScale": 0,
"nullable": "N"
}
},
// SEE EXAMPLE TEST
"TEST": {
"type": "number"
},
"oid": {
"type": "number",
"required": false
},
"lang": {
"type": "number",
"required": true
},
"name1": {
"type": "string",
"required": true
},
"name2": {
"type": "string",
"required": true
},
"deleted": {
"type": "boolean",
"required": true,
"default": false
},
"hidden": {
"type": "boolean",
"required": true,
"default": false
},
"sortscount": {
"type": "number",
"required": true
}
},
"validations": [],
"relations": {
"categories": {
"type": "hasMany",
"model": "genera_category",
"foreignKey": "generaId",
"through": "generas_categories_mm",
"options": {
"nestRemoting": true
}
},
"arts": {
"type": "hasMany",
"model": "art",
"foreignKey": "generaId",
"options": {
"nestRemoting": true
}
},
"sorts": {
"type": "hasMany",
"model": "sort",
"foreignKey": "generaId",
"options": {
"nestRemoting": true
}
}
},
"methods": {}
}
In Addition i've tried runing yarn update tables. This command doesn't run either. It says. Cannot read 'connector' of undefined.
Here is the code inside my drop-tables.js
/* jshint esversion: 6 */
/* jshint node: true */
'use strict';
const server = require('../server/server');
const ds = server.dataSources.mySqlIds;
const models = require('../server/model-config.json');
const tables = [];
const keys = Object.keys(models);
keys.forEach(key => {
const model = models[key];
if (key[0] !== '_' && model.dataSource ===
'mySqlIds') {
tables.push(key);
}
});
const dropTablePromises = [];
console.log(tables);
tables.map(table => {
const dropTable = new Promise(function(resolve,
reject) {
ds.connector.dropTable(
table, function(err, data) {
if (err) {
reject(err);
} else {
console.log(`\tDrop table "${table}".`);
resolve({name: table, data: data});
}
});
});
dropTablePromises.push(dropTable);
});
console.log('1. DROP TABLES');
Promise.all(dropTablePromises)
.catch(err => console.log(err))
.then(() => ds.disconnect());
The result of server.dataSources is an empty object = {}
that explains why it says 'ds' is undefined. (It doesn't work with datasources either).
My goal is just to extend the clients table by a few properties.
Thank you!

Role in loopback not working properly getting error 401

Hello I'm new to loopback and I'm stucked on the Role creation and use.So basically what I'm trying to do is to create 2 roles and based on these roles I want to restrict some users to access some resources.The problem is that on every attempt to get some information from the api I'm getting this
{
"error": {
"statusCode": 401,
"name": "Error",
"message": "Authorization Required",
"code": "AUTHORIZATION_REQUIRED",
"stack": "Error: Authorization Required\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\lib\\application.js:433:21\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\lib\\model.js:359:7\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\acl.js:536:16\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:3888:9\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:473:16\n at iteratorCallback (C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:1064:13)\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:969:16\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\async\\dist\\async.js:3885:13\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\acl.js:518:17\n at C:\\Users\\HP\\Desktop\\battle-horse\\battle-horse\\node_modules\\loopback\\common\\models\\role.js:447:21\n at _combinedTickCallback (internal/process/next_tick.js:131:7)\n at process._tickCallback (internal/process/next_tick.js:180:9)"
}
}
In my application I have 2 models:
1.Client (which extends build in User Model) and has role ```bs_client```
2.Admin(which also extends the build in User Model)
Note that these models were created using loopback cli and has no relationship created yet.
lb model
I'm using Mongodb as database and here is my datasource file
"mongodb": {
"host": "",
"port": 0,
"url": "mongodb+srv://general:234234##/#####?retryWrites=true&w=majority",
"database": "database",
"password": "password",
"name": "mongodb",
"user": "general",
"useNewUrlParser": true,
"includeSubDomains": true,
"useUnifiedTopology": true,
"connector": "mongodb"
}
It seems that the data is being added correctly in my collections (Role, Rolemapping, Client and Access Token).
I'm assigning role to each client dynamically upon creation using this
Client.observe('after save', function setRole(ctx, next) {
if (ctx.instance) {
if (ctx.isNewInstance) {
// look up role based on type
//
app.models.Role.find({where: {name: 'bs_client'}}, function(err, role) {
if (err) { return console.log(err); }
if (role) {
app.models.RoleMapping.create({
principalType: app.models.RoleMapping.User,
principalId: ctx.instance.id,
roleId: role.id,
}, function(err, roleMapping) {
if (err) { return console.log(err); }
console.log('User assigned RoleID ' + role.id + ' (' + ctx.instance.type + ')');
});
};
});
}
} next();
});
and here is my model-config.json
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
],
"mixins": [
"loopback/common/mixins",
"loopback/server/mixins",
"../common/mixins",
"./mixins"
]
},
"User": {
"dataSource": "mongodb",
"public": false
},
"AccessToken": {
"dataSource": "mongodb",
"public": false
},
"ACL": {
"dataSource": "mongodb",
"public": false
},
"RoleMapping": {
"dataSource": "mongodb",
"public": true,
"options": {
"strictObjectIDCoercion": true
}
},
"Role": {
"dataSource": "mongodb",
"public": true
},
"Email": {
"dataSource": "Email"
},
"Client": {
"dataSource": "mongodb",
"public": true
},
}
and in client.json
"acls": [
{
"accessType": "*",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "CLIENT",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "CLIENT",
"principalId": "bs_client",
"permission": "ALLOW"
}
],
Following https://loopback.io/doc/en/lb3/Model-property-reference.html, everything should be working fine, why I'm not able to retrieve "clients" using the configuration above.
Thanks in advance.
This line should look like this everywhere in "acls": "principalType": "ROLE",
example ACL:
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],

Loopback error: Authorization Required

I have a loopback app with mongoDB as below:
when i login as Admin i cannot use post method on dishes. and i get authorization required error.
that becomes possible only when i change the dishes role to ALLOW everyone.
how can i acheive the wanted result with keeping everyone on DENY and only ALLOW certain users to certain operations?
thank you. here is my code..
app/server/model-config.json:
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
],
"mixins": [
"loopback/common/mixins",
"loopback/server/mixins",
"../node_modules/loopback-ds-timestamp-mixin",
"../common/mixins",
"./mixins"
]
},
"User": {
"dataSource": "db"
},
"AccessToken": {
"dataSource": "db",
"public": false
},
"ACL": {
"dataSource": "MongoDB",
"public": false
},
"RoleMapping": {
"dataSource": "MongoDB",
"public": false
},
"Role": {
"dataSource": "MongoDB",
"public": false
},
"dishes": {
"dataSource": "MongoDB",
"public": true
},
"Customer": {
"dataSource": "MongoDB",
"public": true
},
"Comments": {
"dataSource": "MongoDB",
"public": true
}
}
app/common/modles/dishes.json:
{
"name": "dishes",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string",
"required": true
},
"category": {
"type": "string",
"required": true
},
"image": {
"type": "string",
"required": true
},
"label": {
"type": "string",
"required": true,
"default": "''"
},
"price": {
"type": "string",
"required": true,
"default": "0"
}
},
"mixins": {
"TimeStamp": true
},
"validations": [],
"relations": {
"comments": {
"type": "hasMany",
"model": "Comments",
"foreignKey": ""
},
"customers": {
"type": "hasMany",
"model": "Customer",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],
"methods": {}
}
app/common/modles/comments.json:
{
"name": "Comments",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"Rating": {
"type": "number",
"required": true,
"default": 5
},
"comment": {
"type": "string",
"required": true
}
},
"mixins": {
"TimeStamp": true
},
"validations": [],
"relations": {
"dishes": {
"type": "belongsTo",
"model": "dishes",
"foreignKey": ""
},
"customer": {
"type": "belongsTo",
"model": "Customer",
"foreignKey": "customerId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
"methods": {}
}
app/common/modles/customer.json:
{
"name": "Customer",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"comments": {
"type": "hasMany",
"model": "Comments",
"foreignKey": "customerId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
}
],
"methods": {}
}
and app/server/boot/script.js:
module.exports = function(app) {
var MongoDB = app.dataSources.MongoDB;
MongoDB.automigrate('Customer', function(err) {
if (err) throw (err);
var Customer = app.models.Customer;
Customer.create([
{username: 'Admin', email: 'admin#admin.com', password: 'abcdef'},
{username: 'muppala', email: 'muppala#ust.hk', password: 'abcdef'}
], function(err, users) {
if (err) throw (err);
var Role = app.models.Role;
var RoleMapping = app.models.RoleMapping;
Role.find({ name: 'admin' }, function(err, results) {
if (err) { throw err; }
if (results.length < 1) {
// now we know the DB doesn't have it already, so do the Role creation...
//create the admin role
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
}
});
});
});
};
Seeing your last question I imagine what happened.
Somehow the collection Role was created but not mapped to User.
I suggest you to change:
Role.find({ name: 'admin' }, function(err, results) {
if (err) { throw err; }
if (results.length < 1) {
// now we know the DB doesn't have it already, so do the Role creation...
//create the admin role
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
}
});
By:
Role.create({
name: 'admin'
}, function(err, role) {
if (err) throw (err);
//make admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users[0].id
}, function(err, principal) {
if (err) throw (err);
});
});
Drop the Role collection:
db.Role.drop()
and execute Loopback again.
Note: I was doing the same assigment and worked for me.
I am also having the same trouble as I come through the same assignment. Mr Kike's answer works for mine.
First, from cmd, type
>mongo
>use conFusion (Note: conFusion is the name of database of this assignment)
>show collections (Note: to see all collections in database collections)
>db.Role.drop()
And then run the loopback with node . again

ACL troubles with loopback.io

I'm currently evaluating loopback.io for developing the API portion of a new project, and I'm having problems with setting the correct ACL entries.
What I wish to accomplish is given an auth token, the GET endpoints should only return objects owned by the user. For example, a request to /Shows?access_token=xxxxxx should return only objects owned by the user.
Below is my shows.json file, and my User model is named Podcaster. Any help would be appreciated.
{
"name": "Show",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"title": {
"type": "string",
"required": true
},
"description": {
"type": "string"
}
},
"validations": [],
"relations": {
"episodes": {
"type": "hasMany",
"model": "Episode",
"foreignKey": ""
},
"podcaster": {
"type": "belongsTo",
"model": "Podcaster",
"foreignKey": ""
}
},
"acls": [
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
}
],
"methods": {}
}
It's not related to ACL's.
You want to change the business logic of the method. So the best practice is that you create a new method for getting shows owning by current user.
If you want to work your current owner ACl, you need to create a relation between user and show, and set ownerId in the show model.
{
"name": "Show",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"title": {
"type": "string",
"required": true
},
"description": {
"type": "string"
},
"description": {
"type": "string"
}
"ownerId": {
"type": "object"
}
},
"validations": [],
"relations": {
"owner": {
"type": "belongsTo",
"model": "user",
"foreignKey": "ownerId"
},
....

Properties in Model.json

What are the properties that can be mentioned in the Model.json file of a model in loopback(Strongloop) ?
I was trying to figure out what extra properties can be added to the model.json file but in the documentation very less is given.
Certain new fields like – hidden, hiddenProperties, hiddenAPIProperties can be added to the model.json file which are not mentioned in the Loopback documentation……take a look at these
{
"name": "product",
"plural": "products",
"base": "persistedModel",
"strict": true,
"properties": {
"id": {
"type": "text",
"required": true,
"access": "readOnly",
"readOnlyType": "auto"
},
"created": {
"type": "text",
"required": true,
"access": "readOnly",
"readOnlyType": "createdTimestamp"
},
"lastUpdated": {
"type": "text",
"required": true,
"access": "readOnly",
"readOnlyType": "updatedTimestamp"
},
"requestIPAddress": {
"type": "text",
"required": true,
"access": "hidden"
},
"name": {
"type": "text",
"required": true,
},
"type": {
"type": "text",
"required": true
},
"manufacturerId": {
"required": true
},
"supplierId": {
"required": true
}
},
"hidden": [],
"hiddenProperties": [],
"hiddenAPIProperties": [],
"validations": [],
"relations": {
"manufacturer": {
"type": "belongsTo",
"model": "manufacturer",
"foreignKey": "manufacturerId"
},
"supplier": {
"type": "belongsTo",
"model": "supplier",
"foreignKey": "supplierId"
}
},
"acls": [
{
"accessType": "*",
"modelProperty": "manufacturerId"
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
},
{
"accessType": "*",
"modelProperty": "supplierId"
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
},
{
"property": "_READ_HIDDEN_PROPERTIES_",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "admin"
},
{
"property": "_WRITE_HIDDEN_PROPERTIES_",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "admin"
},
{
"accessType": "*",
"modelProperty": "manufacturerId"
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "admin"
},
{
"accessType": "*",
"modelProperty": "supplierId"
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "admin"
},
{
"accessType": "*",
"modelProperty": "manufacturerId"
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "productAdmin"
},
{
"accessType": "*",
"modelProperty": "supplierId"
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "productAdmin"
}
],
"methods": []
}

Resources