Strongloop Loopback Native SQL - mysql

I have the following situation in a loopback application:
I have a 'Person' class that extends from built in 'User'
I have students ('Person' class with Role 'student')
I have teachers ('Person' class with Role 'teacher')
I have classgroups (ClassGroup class related to Person class with hasManyAndBelongsTo)
Students and Teachers are added to classgroups in the same way. Teachers can have multiple classgroups, students not. If I want to see all the students that are in the same classgroups, as a teacher, I use a remote method on Person. In that method I use the following SQL (21 is the id of the teacher):
SELECT * FROM `Person`
JOIN `ClassGroupPerson`
ON `Person`.`id` = `ClassGroupPerson`.`personId`
JOIN `RoleMapping`
ON `RoleMapping`.`principalType` = "USER"
AND `RoleMapping`.`principalId` = `Person`.`id`
JOIN `Role`
ON `Role`.`name` = "student"
AND `Role`.`id` = `RoleMapping`.`roleId`
WHERE
`ClassGroupPerson`.`classGroupId` IN (
SELECT `classGroupId`
FROM `ClassGroupPerson`
WHERE `personId` = 21
)
In the documentation, it says, that using native Mysql is discouraged (http://docs.strongloop.com/display/public/LB/Executing+native+SQL).
How would I achieve the same result in 'Loopback-style'. I've tried with filters, include, hasManyTrough, ... -stuff but I'm not able to achieve the same result?
Extra question: the result from this are JSON objects, how can I convert those easily into Person objects?
EDIT
With the following code in my remote method, I'm getting close:
Person.find({
include: {
relation: 'classgroups',
scope: {
include: {
relation: 'people'
}
}
},
where: {
id: id
}
}, function(err, people){
cb(err, people);
});
I have as a result: my teacher, with his classgroups included, and with each classgroup, I have the connected persons (teachers and students). The problem that remains, is that the teacher is also included in the classrooms, so I should be able to filter the result by Role = 'student', and I don't see how to do that?

Related

Prisma many to many self relation error due to #relation and generated type input

Using Prisma 3.7.0.
I'm following this example in the docs
model Person {
id Int #id #default(autoincrement())
name String?
followers Follows[] #relation("follower")
following Follows[] #relation("following")
}
model Follows {
follower Person #relation("follower", fields: [followerId], references: [id])
followerId Int
following Person #relation("following", fields: [followingId], references: [id])
followingId Int
##id([followerId, followingId])
}
Then I try creating a User along with the people they're following, with the below code.
const person = await prisma.person.create({
data: {
following: {
create: [
{ following: { connect: { id: 1 } } }
]
}
}
});
I'm getting the following (see what I did there :)) error.
/usr/src/app/node_modules/#prisma/client/runtime/index.js:34755
const error2 = new PrismaClientValidationError(renderErrorStr(validationCallsite));
^
PrismaClientValidationError: Unknown arg `following` in data.following.create.0.following for type FollowsCreateWithoutFollowingInput. Did you mean `follower`?
Argument follower for data.following.create.0.follower is missing.
at Object.validate (/usr/src/app/node_modules/#prisma/client/runtime/index.js:34755:20)
at PrismaClient._executeRequest (/usr/src/app/node_modules/#prisma/client/runtime/index.js:39749:17)
at consumer (/usr/src/app/node_modules/#prisma/client/runtime/index.js:39690:23)
at /usr/src/app/node_modules/#prisma/client/runtime/index.js:39694:49
at AsyncResource.runInAsyncScope (node:async_hooks:199:9)
at PrismaClient._request (/usr/src/app/node_modules/#prisma/client/runtime/index.js:39694:27)
at request (/usr/src/app/node_modules/#prisma/client/runtime/index.js:39799:77)
at _callback (/usr/src/app/node_modules/#prisma/client/runtime/index.js:40007:14)
at PrismaPromise.then (/usr/src/app/node_modules/#prisma/client/runtime/index.js:40014:23) {
clientVersion: '3.7.0'
}
I believe the reason I'm getting this error is because the following[] relation creates an input as mentioned in the error message FollowsCreateWithoutFollowingInput, meaning it's expecting a follower relation and not a following, as the following will be the Person I'm currently creating, and I just need to tell it who is the follower Person.
However this doesn't make sense to me. When I'm creating a Person along with its people they're following, I understand that to be an array of Persons who the person I'm currently creating is following. If so, then a Follows record in the following array contains the current Person (the one I'm creating) as the follower relation and some other Person as the following relation. And the relation I should be inputting is the following and not the follower. Therefore the input type that prisma should generate should be FollowsCreateWithoutFollowerInput instead of FollowsCreateWithoutFollowingInput.
What am I missing, in my understanding?
I looked at the below resources during my research on this.
count self relation on Prisma error: table name specified more than once. This is discussing a different issue, using the same example.
https://github.com/prisma/prisma/discussions/3960. This discusses how to create the same type of relation, where the join table references the same table for both ids. But it doesn't explain how to create records once the relationships are defined.
One-to-many self-relation in prisma schema. This shows how to create a record, but its not using the join tables relation during create it's using a property. Also it's not exactly the same case as the join table seems to be the same table.
`const person = await prisma.person.create({
data: {
following: {
connect: { id: 1 }
}
}
});
It's the "create" in the body of the function that's doing it. The second nested following would also cause a problem if you just fix that though.
Think of it like you aren't creating the follower. The follower already needs to be there. You are just creating a record of the connection between the person and the follower. So prisma.person.create some data that this person has a follower and that follower is connected to the user with id whatever.

Error in setting up laravel Eloquent relationship

I am trying to set up a relationship in Laravel using the eloquent relationship model, it looks like the one below
users
id
email
projects
id
name
user_projects
user_id
project_id
role
I want to be able to query the projects that the user is a part of irrespective of the role that he has. To do this, I used the following in my user model.
class User extends Model {
public function allProjects() {
return $this->belongsToMany('App\ProjectRoles', 'user_projects', 'user_id', 'project_id');
}
}
But because of this I get an error in my code
Syntax error or access violation: 1066 Not unique table/alias: 'user_projects' (SQL: select `user_projects`.*, `user_projects`.`user_id` as `pivot_user_id`, `user_projects`.`project_id` as `pivot_project_id` from `user_projects` inner join `user_projects` on `user_projects`.`id` = `user_projects`.`project_id` where `user_projects`.`user_id` = 2)
I am not sure how to create the join query in the relationships in order to do it. Any help would be appreciated. I went through the eloquent docs but wasn't able to get the result even though the examples there do exactly what I want.
Your belongsToMany relationship is related to the wrong Model. Generally you don't have a model for your pivot table, but even if you do, your belongsToMany relationship doesn't use it. Your User model should be related to your Project model.
You should use this:
class User extends Model {
public function allProjects() {
return $this->belongsToMany('App\Project', 'user_projects', 'user_id', 'project_id');
}
}

How to count association size with waterline/sails?

Using sails 0.10.5/waterline 0.10.15:
I cannot find an answer to a simple question: how to count the elements of an association without using populate() (which would load all data).
Let take a simple many2many relation with via:
User:
attributes: {
following: {
collection: 'user',
via: 'follower',
dominant: true
},
follower: {
collection: 'user',
via: 'following'
}
Now I need the size of the collections.
Currently I try
User.findById(1).populateAll().exec(function(err, user) {
// count of followings -> user.following.length;
// count of followers-> user.follower.length;
}
which leads to loading the collections.
I'm missing a count function at collection level to avoid population/loading of data.
Is there a possibility to access the (auto generated) join tables to run a count-query directly on the join?
Something like:
User.findById(1).count({'followings'}).exec(function(err, followings) {
...}
or
UserFollowingFollow_FollowFollowing.countByUserFollowingFollowId(1).
exec(function(err, followings) {
...}
Waterline does offer the count query method and it can be used like this to solve your problem:
User.count().where({follower: followerId})
.exec(function(err, numberOfFollowings) {
//numberOfFollowings will be the integer that you need
})
followerId is the id that you are passing to User.findOne() in your example.
You can also read the Waterline documentation about this.

Row level permissions in sails.js

I need row-level permissions in sails. I already looked at this and at other threads, but everybody says to just use policies.
But what I need isn't just limiting the ability to do e.g. find/update all elements of a table but to have permissions per database row that consider associations for permissions.
It would be great if the permissions support blueprints like the ember data adapter, nested create/update/find (with populate) and sockets.
So for example:
GET /:model/:id
should return and populate with such entries where certain associated conditions are met.
So for example, we have 4 models:
User (columns: id, name, email, pwd_hash, ...)
Project (columns: id, client, name, ...)
UserAssignment (columns: id, user, project, user_perms, ...)
Client (columns: id, name, ...)
User and Project are linked through UserAssignment - an advanced MM-Table. (Users may have special user_perms to different projects, such as read,write,manage). And a Project always has one Client.
Here's the corresponding sails models:
// User.js
attributes: {
name: 'string'
}
// Project.js
attributes: {
name: 'string',
client: {
model: 'client'
},
userAssignments: {
collection: 'userAssignment',
via: 'project'
}
}
// UserAssignment.js
attributes: {
userPerms: 'integer',
user: {
model:'user'
},
project: {
model:'project'
}
}
// Client.js
attributes: {
name: 'string',
projects: {
collection: 'project',
via: 'client'
}
}
So lets say the User with the ID=1 wants to access a list of Clients he is allowed to see, he calls
GET /clients/
Speaking in SQL:
SELECT client.*
FROM client
INNER JOIN project ON project.client = client.id
INNER JOIN user_assignment ON project.id = user_assignment.project
WHERE user_assignment.user = 1 and user_perms > 4
GROUP BY client.id;
And then also if we have certain Project managers, they can update associated UserAssignments etc.
Basically I thought the permissions could be based on role associations.
I tried several ways to implement this functionality. I tried _permission_read, _permission_write columns for all rows and other stuff like using populates for this but nothing seems to work right.
The above example is just a excerpt of many different kinds of models which I can filter based on SQL couldn't do nicely with Sails/Waterline.
Do I need custom SQL queries for this?
Is it possible to do this neatly with Sails?
Do I misunderstand policies and is there a way to implement such requirements with them?
Or shall I use SQL views instead of tables?
Thanks in advance!

MySQL many-to-many relationship in LoopBack

I'm having trouble getting LoopBack to perform a many-to-many join query. Considering the hasManyThrough example from the documentation:
var Physician = ds.createModel('Physician', {name: String});
var Patient = ds.createModel('Patient', {name: String});
var Appointment = ds.createModel('Appointment', {
physicianId: Number,
patientId: Number,
appointmentDate: Date
});
Appointment.belongsTo(Patient);
Appointment.belongsTo(Physician);
Physician.hasMany(Patient, {through: Appointment});
Patient.hasMany(Physician, {through: Appointment});
If I try to do a single search to find Patients associated with a particular doctor who have a zip code of 10012, I could try:
physician.patients({where: {zip: 10012}}, fn);
However, the search on the physician's patients is actually only searching on the Appointments table. Is there any way to do a simple search that will be performed on the specific physician's patients directly?
LoopBack implements the hasMany/through relation for physician.patients() as follows:
Appointment.find ({ where: { physicianId: 1 },
include: 'patient',
collect: 'patient' }, callback);
We're considering to support the filter for the 'include' which brings int 'patient' information.
I suggest you open an issue at https://github.com/strongloop/loopback-datasource-juggler/issues.