Loopback 4 delete relation - relational-database

I am staring with lp4, i I have following two models
Product
ProductTranslations
the Product hasMany relation with the ProductTranslations model
How can i delete product, ProductTranslations will be deleted corresponding to product

What you're looking for is cascade delete. LoopBack 4 does not support this out-of-the-box (see issue #3526).
As a workaround, you may override the .delete() Repository function to start a Transaction and emulate a cascade delete with full ACID compliance.

#del('/users/{id}')
#response(204, {
description: 'User DELETE success',
})
async deleteById(#param.path.string('id') id: string): Promise<void> {
await this.userRepository.deleteById(id);
}

Related

Create or update one to many relationship in Prisma

I'm trying to update a one to many relationship in Prisma. My schema looks like this
model A_User {
id Int #id
username String
age Int
bio String #db.VarChar(1000)
createdOn DateTime #default(now())
features A_Features[]
}
model A_Features {
id Int #id #default(autoincrement())
description String
A_User A_User? #relation(fields: [a_UserId], references: [id])
a_UserId Int?
}
I'm trying to add a couple of new features to user with id: 1, or update them if they are already there.
I'm trying doing something like
const post = await prisma.a_User.update({
where: { id: 1},
data: {
features: {
upsert: [
{ description: 'first feature'},
{ description: 'second feature'}
]
}
}
})
The compiler isn't happy, it tells me
Type '{ features: { upsert: { description: string; }[]; }; }' is not assignable to type '(Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput)'.
Object literal may only specify known properties, and 'features' does not exist in type '(Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput)'.ts(2322)
index.d.ts(1572, 5): The expected type comes from property 'data' which is declared here on type '{ select?: A_UserSelect; include?: A_UserInclude; data: (Without<A_UserUpdateInput, A_UserUncheckedUpdateInput> & A_UserUncheckedUpdateInput) | (Without<...> & A_UserUpdateInput); where: A_UserWhereUniqueInput; }'
(property) features: {
upsert: {
description: string;
}[];
}
I can't work out how to do it nor I can find clear help in the documentation. Any idea on how to implement it or where I can find some examples?
I'm providing my solution based on the clarifications you provided in the comments. First I would make the following changes to your Schema.
Changing the schema
model A_User {
id Int #id
username String
age Int
bio String #db.VarChar(1000)
createdOn DateTime #default(now())
features A_Features[]
}
model A_Features {
id Int #id #default(autoincrement())
description String #unique
users A_User[]
}
Notably, the relationship between A_User and A_Features is now many-to-many. So a single A_Features record can be connected to many A_User records (as well as the opposite).
Additionally, A_Features.description is now unique, so it's possible to uniquely search for a certain feature using just it's description.
You can read the Prisma Guide on Relations to learn more about many-to-many relations.
Writing the update query
Again, based on the clarification you provided in the comments, the update operation will do the following:
Overwrite existing features in a A_User record. So any previous features will be disconnected and replaced with the newly provided ones. Note that the previous features will not be deleted from A_Features table, but they will simply be disconnected from the A_User.features relation.
Create the newly provided features that do not yet exist in the A_Features table, and Connect the provided features that already exist in the A_Features table.
You can perform this operation using two separate update queries. The first update will Disconnect all previously connected features for the provided A_User. The second query will Connect or Create the newly provided features in the A_Features table. Finally, you can use the transactions API to ensure that both operations happen in order and together. The transactions API will ensure that if there is an error in any one of the two updates, then both will fail and be rolled back by the database.
//inside async function
const disconnectPreviouslyConnectedFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
set: [] // disconnecting all previous features
}
}
})
const connectOrCreateNewFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
// connect or create the new features
connectOrCreate: [
{
where: {
description: "'first feature'"
}, create: {
description: "'first feature'"
}
},
{
where: {
description: "second feature"
}, create: {
description: "second feature"
}
}
]
}
}
})
// transaction to ensure either BOTH operations happen or NONE of them happen.
await prisma.$transaction([disconnectPreviouslyConnectedFeatures, connectOrCreateNewFeatures ])
If you want a better idea of how connect, disconnect and connectOrCreate works, read the Nested Writes section of the Prisma Relation queries article in the docs.
The TypeScript definitions of prisma.a_User.update can tell you exactly what options it takes. That will tell you why the 'features' does not exist in type error is occurring. I imagine the object you're passing to data takes a different set of options than you are specifying; if you can inspect the TypeScript types, Prisma will tell you exactly what options are available.
If you're trying to add new features, and update specific ones, you would need to specify how Prisma can find an old feature (if it exists) to update that one. Upsert won't work in the way that you're currently using it; you need to provide some kind of identifier to the upsert call in order to figure out if the feature you're adding already exists.
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#upsert
You need at least create (what data to pass if the feature does NOT exist), update (what data to pass if the feature DOES exist), and where (how Prisma can find the feature that you want to update or create.)
You also need to call upsert multiple times; one for each feature you're looking to update or create. You can batch the calls together with Promise.all in that case.
const upsertFeature1Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const upsertFeature2Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const [results1, results2] = await Promise.all([
upsertFeaturePromise1,
upsertFeaturePromise2
]);

How to get associated elements in Sequelize for specific case

I have models for Users, Projects and Tasks
Users and Tasks has association Many-to-Many
Projects and Tasks has association One-to-Many (each Project has many Tasks)
When receiving Tasks for specified User, I want to get associated Project for each task.
May anybody help to find solution how yo to this.
My code is below:
// receiving of user from database
const user = await User.findOne({where: {id: request.userId}})
// receiving of tasks related to user
const tasks = await user.getTasks(
// here i've tried to add following code, but it does not works
include: { model: Project, as: 'project' }
)
response.status(201).json( tasks )
Any help would be thankful
In Sequelize, when dealing with 'many' associations, might become a little different. You said you already have this association:
Project.model
Project.hasMany(Task)
Now you need to implement the other way inside your Task.model:
Task.belongsTo(Project);
In the Sequelize Documentation you'll find this:
To create a One-To-Many relationship, the hasMany and belongsTo
associations are used together;
I have fixed problem. Correct code below.
const user = await User.findByPk( request.userId )
if(!user) return
const tasks = await user.getTasks(
{ include: [
{ model: Project, as: 'project' }
] }
)

Add foreign key index Loopback 4 MySQL

i am new in Loopback 4
I followed this tutorial to get started and everything worked fine.
I tried to create my own models Category and SubCategory using a MySQL database, with one to many relation (one category has many sub categories), i have noticed that it did create a field in subcategory table (categoryId) but the foreign key index is missing.
can someone help?
LoopBack 4 does not implicitly add foreign key constraints. This is to allow weak cross-datasource relations (e.g. a relation between PostgreSQL & Oracle).
Hence, the responsibility falls on the connectors to provide an interface to define these constraints. This however, means that there isn't a consistent interface across different connectors. There is an open issue to track this.
In the case of MySQL:
#model({
settings: {
foreignKeys: {
categorySubCategoryFK: {
name: 'categorySubCategoryFK',
entity: 'Category',
entityKey: 'id',
foreignKey: 'categoryId',
},
},
},
})
In case auto-migration is used (which it should not be used in production!), migrate.ts would need to be updated to define explicit ordering of the schemas:
await app.migrateSchema({
existingSchema,
models: ['Category', 'SubCategory'],
});
Further reading
https://loopback.io/doc/en/lb4/todo-list-tutorial-sqldb.html#specify-the-foreign-key-constraints-in-todo-model
https://loopback.io/doc/en/lb4/MySQL-connector.html
found the answer here, besides adding the settings to #model annotation, like so
#model({
settings: {
foreignKeys: {
categorySubCategoryFK: {
name: 'categorySubCategoryFK',
entity: 'Category',
entityKey: 'id',
foreignKey: 'categoryId',
},
},
},
})
you have to change to specify in which order tables should be created in migrate.ts and change this
await app.migrateSchema({existingSchema});
to this
await app.migrateSchema({
existingSchema,
models: ['Category', 'SubCategory'],
});
more details here
Loopback 4 creates relation on models and Api's not on database. Thus it won't get reflected on your mysql database

How do you insert / find rows related by foreign keys from different tables using Sequelize?

I think I've done enough research on this subject and I've only got a headache.
Here is what I have done and understood: I have restructured my MySQL database so that I will keep my user's data in different tables, I am using foreign keys. Until now I only concluded that foreign keys are only used for consistency and control and they do not automatize or do anything else (for example, to insert data about the same user in two tables I need to use two separate insert statements and the foreign key will not help to make this different or automatic in some way).
Fine. Here is what I want to do: I want to use Sequelize to insert, update and retrieve data altogether from all the related tables at once and I have absolutely no idea on how to do that. For example, if a user registers, I want to be able to insert the data in the table "A" containing some user information and in the same task insert in the table B some other data (like the user's settings in the dedicated table or whatever). Same with retrievals, I want to be able to get an object (or array) with all the related data from different tables fitting in the criteria I want to find by.
Sequelize documentation covers the things in a way that every thing depends on the previous one, and Sequelize is pretty bloated with a lot of stuff I do not need. I do not want to use .sync(). I do not want to use migrations. I have the structure of my database created already and I want Sequelize to attach to it.
Is it possible insert and retrieve several rows related at the same time and getting / using a single Sequelize command / object? How?
Again, by "related data" I mean data "linked" by sharing the same foreign key.
Is it possible insert and retrieve several rows related at the same
time and getting / using a single Sequelize command / object? How?
Yes. What you need is eager loading.
Look at the following example
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Address = sequelize.define('add', {
address: Sequelize.STRING,
});
const Designation = sequelize.define('designation', {
designation: Sequelize.STRING,
});
User.hasOne(Address);
User.hasMany(Designation);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test123',
add: {
address: 'this is dummy address'
},
designations: [
{ designation: 'designation1' },
{ designation: 'designation2' },
],
}, { include: [Address, Designation] }))
.then(user => {
User.findAll({
include: [Address, Designation],
}).then((result) => {
console.log(result);
});
});
In console.log, you will get all the data with all its associated models that you want to include in the query

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!