Better way to perform this Relation "transaction" in Prisma - prisma

I posted a question yesterday that has the relevant prisma schema which can be found here.
As a follow up question, when a member creates a new Organization, I'd like for it to become their selected Membership. The only way I've found to do this is to deselect their current Memebership (set it to null), do the create, then restore the relationship if that create didn't work. I have to use updateMany for that initial operation in case there is no selectedMembership. Is that right?
//Deselect the currently selected Org
const updatedMembership = await prisma.membership.updateMany({
where: {
selectedById: user.id
},
data: {
selectedById: null
}
});
if (updatedMembership) {
//Select the new one.
const result = await prisma.organization.create({
data: {
name: body.name,
members: {
create: [{
role: MemberRole.OWNER,
userId: user.id,
selectedById: user.id
}]
}
},
});
if (result) {
res.status(200)
.json(result);
} else {
//Restore the previously selected one if the create failed
if(user.selectedMembership) {
await prisma.membership.update({
where: {
id: user.selectedMembership?.id
},
data: {
selectedById: user.id
}
});
}
res.status(500).end();
}
}

You can use the connect API to do all of this in a single query. Just make sure that the user.id is valid.
Here's a much cleaner version of the create and update query logic in your question:
const result = await prisma.organization.create({
data: {
name: body.name,
members: {
create: {
role: MemberRole.OWNER,
user: {
connect: {
id: user.id, // making the user a member of the organization
},
},
selectedBy: {
connect: {
id: user.id, // selecting the newly created membership as the user's default organization
},
},
},
},
},
});
This will handle all cases, regardless of whether the user with id = user.id currently:
Is a member of other organization(s) and has another membership as their default
Is a member of other organization(s) but has no default membership
Is not a member of any organization and has no default membership

Related

How to find or create a record with Prisma?

Is this the best way to find or create a user in Prisma?
prisma.user.upsert({
where: {
id: user.id,
},
update: {
id: user.id,
},
create: {
id: user.id,
},
})
Yes, you can use upsert to create a record. If the update property is empty, the record will not be updated.
Example:
const upsertUser = await prisma.user.upsert({
where: {
email: 'test#prisma.io',
},
update: {},
create: {
email: 'test#prisma.io',
name: 'Test User',
},
})
We plan to document this better: how upsert can behave as a findOrCreate. The link to the GitHub issue can be found here
For more information regarding upsert, you can read more in the Prisma Client API Reference section.

Prisma splice Item from Array

I have been pushing updates to an array and was wondering if there is a built-in method to remove an entry from the array as well. Basically reversing the push command. I suspect that I have to query all documents and remove the item myself. But maybe there is some functionality I was unable to find inside the documentation.
Push:
const addTag = await prisma.post.update({
where: {
id: 9,
},
data: {
tags: {
push: 'computing',
},
},
})
Remove Expectation:
const removeTag = await prisma.post.update({
where: {
id: 9,
},
data: {
tags: {
splice: 'computing',
},
},
})
As of writing, there's no method to splice/remove items from a scalar list using Prisma. You would have to fetch the scalar list from your database, modify it manually in your application code and overwrite the record in your database with an update operation.
There is a feature request for this, please feel free to follow/comment with your use-case to help us track demand for this feature.
const { dogs } = await prisma.user.findOne({
where: {
id: userId
},
select: {
dogs: true
},
});
await prisma.user.update({
where: {
id: userId
},
data: {
dogs: {
set: dogs.filter((id) => id !== 'corgi'),
},
},
});

Pulling an object from user Model using $pull , having issues with multiple object items

I am trying to delete a post object from a user model, I hold these refrences to the post they have created, this is how I am trying to currently pull the post
userModel.findOneAndUpdate(
{ email: req.query.email, posts: req.query.postid },
// { $pull: { posts: req.query.postid } },
{ $pull: { posts : { number: mongoose.Types.ObjectId(req.query.postid) } }},
{ new: true },
function (error, user) {
if (error) {
res.json("error in /testing backend ===",error)
}
console.log(`Post id ===== ${req.query.postid}`);
console.log(`Email===== ${req.query.email}`);
console.log(`returning user====${user}`)
res.json('Successfully updated user');
}
);
this is how I have created the post
userModel.findOne({ email: req.body.author }, function(error, user) {
const locationURL = req.files.map((item) => item.location);
postModel.create({ ...req.body, image: locationURL }, (error, returnedDocuments) => {
if (error) {
throw new Error(error);
}
user.posts.push({ number: returnedDocuments._id, title: req.body.title, image: locationURL });
user.save((err) => {
console.log(err);
});
});
I originally had only 1 item pushed into the user model, but added a few more items, then I was having issues pulling the object, thanks for your help.
this is from my DB as to my posts array
For an array of objects, you can pull your desired document using the positional operator { "<array>.$" : value }.
{ $pull: { posts.$.number : req.query.postid }}
You can check out the docs on positional operators to learn more.

Mongodb - Users, roles and groups with JWT + REST API

Intro
I am implementing simple MEAN app with JWT token for auth. This app allows you to create account and then create your own group or join existing one. Each group have specific roles that group administrator assign to each user. Here is my Schemas, that will hopefully help you better understand my solution.
User schema
_id: {
type: String,
default: function () { return new mongo.ObjectId().toString() }
},
email: {
type: String,
required: true,
unique: true
},
displayName: {
type: String
},
hash: {
required: true,
type: String
}
Group schema
_id: {
type: String,
default: function () { return new mongo.ObjectId().toString() }
},
groupName: String,
ownerUser: {
type: String,
ref: 'User'
},
userList: [
{
user: {
type: String,
ref: 'User'
},
role: {
type: String,
ref: 'Role'
}
}
]
Role schema
_id: {
type: String,
default: function () { return new mongo.ObjectId().toString() }
},
groupId: String,
roleName: String,
access: [{
path: String,
allowed: Boolean
}]
How it works
User register/login -> Create group or join existing one -> Group admin assign role to user.
Groups will have approximately 250 users.
Group could have multiple roles (10-15).
User can join multiple groups
Express part
example route
router.get('/restricted', JWTAuth, userProject, userRole, (res, req, next) => {
res.send("ok")
})
access.js
export const JWTAuth = passportAuth('jwt', {session: false});
export const userProject = async (req, res, next) => {
const userProject = await GroupModel.findOne({ _id: req.body.groupId, "userList.user" : req.user._id })
if(userProject) {
req.project = userProject;
next()
}else{
res.sendStatus(401);
}
}
export const userRole = async (req, res, next) => {
const roleId = arrayFindByObjectValue(req.user._id, 'user', req.project.userList).role;
const userRole = await RoleModel.findOne({ _id: roleId })
req.role = userRole;
next();
}
export const arrayFindByObjectValue = (value, key, array) => {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
}
other tries..
populate (any improvements at speed)
export const userRolePopulate = async (req, res, next) => {
const userProject = await GroupModel.findOne({ _id: req.body.groupId, "userList.user" : req.user._id }, {"userList.$" : 1}).populate('userList.role');
req.role = userProject.userList[0].role;
next()
}
and aggregation (15% improvement)
const userProject: any = await GroupModel.aggregate([
{$match: {_id: req.body.groupId}},
{$project: {
userList: {$filter: {
input: '$userList',
as: 'user',
cond: {$eq: ['$$user.user', req.user._id]}
}},
_id: 0
}},
])
Question
Now I hope you understand my situation. With this approach I have to do multiple db queries to get user role just for simple request. I am worried about the speed. Each request took around 150 ms more with role middleware (1000 Group users). I think this solution will unnecessarily overloads the server. Is there better solution? Should I use some session(stateless JWT..) or other method to temporary store user role? Is there better database design for this method?
I read a lot of articles about role based systems, but I have never came across this specific solution. Thank you for your answers. If you need more informations please let me know and I will edit my answer.

Creating a many-to-many relationship in MongoDB/ Mogoose using nested fields?

I'm currently trying to wrap my head around creating a many-to-many relationship in mongodb/mongoose.
I have two Schemas:
// User Schema
var UserSchema = mongoose.Schema({
email: {
type: String,
index:true
},
name: {
type: String
},
tasks:[{type:mongoose.Schema.ObjectId, ref: 'Tasks'}]
});
and
// Tasks Schema
var TaskSchema = mongoose.Schema({
name: {
type: String,
index: true
},
description: {
type: String
},
status:{
type: String
},
});
The idea here, is that each user can take on a task and the task would have it's own status for each user (e.g. notStarted, inProgress, Complete, Failed). This status would change as the user progresses through the task. Each user would see the same tasks(i.e. name + description), but would have a different status associated with it. Additionally, each task has it's own status (Available, Hidden), which would dictate if the users can see the task or not. This would not be unique for each user.
This is my thought process so far:
I'm thinking that I can nest the objectIds of every task in with the user along with the status. For example:
{
email: "Bob#bob.com"
name: "Bob",
tasks: {
{ _id: 001, status: "Active"},
{_id: 002, status: "inProgress"},
{ _id: 003, status: "Failed"}
}
},
{
email: "Mary#mary.com"
name: "Mary",
tasks: {
{ _id: 001, status: "Failed"},
{ _id: 002, status: "Active"},
{ _id: 003, status: "Active"}
}
}
However, this means that whenever I create a new task, I need to add it to the task list of all users, with the status set to a default (e.g. notStarted). Additionally whenever I add a new user, I need to get all tasks and add them to the user's list of tasks.
This seems sort of clunky to me, and I feel like there must be a better way. If this is the best way to do it, I'm not quite sure what I would use to write this. I was thinking maybe use addFields or would Push be more appropriate? Or would that create arrays that grow without bound? which might not be the best idea since it's a Mongo anti-pattern from what I've read?
I also found this post that's sort of related, but it's from ~6 years ago, and I think I need a little bit more depth or code examples.
Any help would be much appreciated!
If you want to add new task in user whenever new task will created then your query should be :
taskCtr.create = (req, res) => {
const {
name,
description,
status
} = req.body;
TaskSchema.create({
name,
description,
status
}).then((result) => {
if (result) {
UserSchema.update({}, {
$push: {
tasks: {
_id: result._id,
status: result.status
}
}
}, {
multi: true
}).then((userUpdated) => {
res.status(200).json({
message: 'A new Task created successfully!'
})
}).catch((err) => {
res.status(500).json({
error: 'Internal server error!'
});
});
}
}).catch((err) => {
res.status(500).json({
error: 'Internal server error!'
});
});
};