Prisma specific one to many relation - database

I am a newbie with Prisma and would like to see how would prisma user do such a call.
I have the following models:
model ChallengeScore {
id Int #id #default(autoincrement())
user User #relation(fields: [username], references: [username])
username String
challenge Challenge #relation(fields: [challengeAddress], references: [address])
challengeAddress String
isGasScore Boolean
isByteScore Boolean
score Int
}
model Challenge {
address String #unique
id Int #id #default(autoincrement())
description String
title String
challengeScores ChallengeScore[]
}
What I want is fetch the challengeScores of a specific user for specific address where isGasScore is true and where score is the lowest. I want to do the same thing for isByteScore is true and score is the lowest.
I also want to fetch for the same specific address the 5 lowest scores (for any users) where isByteScore is true and where is isGasScore is true.
Is it doable in one call? If so what would it look like?
Also what is best practice, should I just fetch all the challengeScores and then filter it out the way I want on the front-end? I am guessing the first option is more costy db wise?
Any advice is appreciated !
Thank you

I would do it in a single transaction, or Promise.all() works too
const username = 'U1';
const challengeAddress = 'C1';
const [lowestGas, lowestByte, lowestFive] = await prisma.$transaction([
prisma.challengeScore.findFirst({
where: { username, challengeAddress, isGasScore: true },
orderBy: { score: 'asc' },
}),
prisma.challengeScore.findFirst({
where: { username, challengeAddress, isByteScore: true },
orderBy: { score: 'asc' },
}),
prisma.challengeScore.findMany({
where: {
username,
challengeAddress,
isGasScore: true,
isByteScore: true,
},
orderBy: { score: 'asc' },
take: 5,
}),
]);
Whether to do filtering on frontend / backend / query differs for each implementation, and depends on the amount of data, filter complexity, reusability, data security etc.
If it is a small application it should be okay to filter in the frontend, but it wouldn't scale well in large applications.

Related

How to use upsert with Prisma?

I'm trying to either update or create a record in my Profile table. I'm using Prisma to define the schema and it looks like this:
model Profile {
id String #id #default(cuid())
username String?
about String?
url String?
company String?
userId String
user User #relation(fields: [userId], references: [id])
}
I'm calling the upsert function like this:
const createOrUpdateProfile = await prisma.profile.upsert({
where: { id: id },
update: {
username: username,
about: about,
url: url,
company: company,
},
create: {
username: username,
about: about,
url: url,
company: company,
user: { connect: { id: userId } },
},
});
I'm getting userId from session:
const userId = session.user.id;
I'm getting id, username, about, url, and company from:
//Passed on from await fetch in the form
const { id, username, about, url, company } = req.body;
The issue I'm having is, whenever I'm trying to provide the id, as in where: { id: id }, and it is not already in the database, it doesn't create a new record.
If the id is not in the database, and I do a console.log(id), it gets back as undefined.
If I manually add a record in Profile and connect the user, it updates the record when calling the upsert function.
Can you help me spot what I'm doing wrong?
If I understand your question correctly, the request body may optionally contain an id or be undefined. If the id is present in the request, you want to update the corresponding record. If it isn't, you want to create a new record.
upsert functionality requires a unique identifier that can be used by the database to check whether there is a matching record. The database will then decide what to do. However with an undefined value, prisma cannot work.
You could use a key that will not exist in the database in case the value of id is undefined to make upsert insert the record in this case.
await prisma.profile.upsert({
where: { id: id || '' },
// ...
});
Or you could differentiate the cases and treat them appropriately:
if (id) {
await prisma.profile.update({
where: { id },
data: {
username,
about,
// ...
},
});
} else {
await prisma.profile.create({
data: {
username,
about,
// ...
},
});
}
You might want to think about the case that there is an id given in the body, but it does exist in the database. Do you want the update to fail and reply with an error? Or do you want the database to silently insert? Then use upsert instead of update above.

How to delete from database where multiple conditions are met?

I'm trying to delete a row in my database using Prisma, where I want to delete it only if two conditions are met, slug and userId.
This is what I have tried to do:
const deleteComment = await prisma.favorites.delete({
where: {
slug: slug,
userId: userId,
},
});
This is my model in schema.prisma:
model Favorites {
id Int #id #default(autoincrement())
slug String #db.VarChar(128)
contentType String? #db.VarChar(128)
userId String
user User? #relation(fields: [userId], references: [id])
}
If I remove the userId: userId, it deletes all rows containing the same slug, which is not ideal if multiple users have added the same slug.
How can I delete a row when both conditions are met?
If you don't specify attributes sufficiently to identify a unique row (guaranteed by the schema), you should use deleteMany instead of delete.
await prisma.favorites.deleteMany({ where: { slug: slug, userId: userId } });

Updating model with GraphQL Mutation

I have been having trouble figuring out how to update a User with graphQL. The functionality I'm currently aiming for is for the user to be able to update their account/profile information. I have some things set up for the user like a bio field for their profile, and a profile picture field that's set up to take a URL and display that as their profile picture.
I have no problems when it comes to creating using graphQL. A user can sign up, log in, make posts, etc without issue. I can also update the User in regards to other models, for example, a new post pushes to the users Post data just fine.
I have not been able to figure out how to update a user directly though. Essentially I can get around this by creating a new model for "profile pic" and pushing that to the User, but that seems like it's just extra steps that might slow things down, as well as shortchanging myself being able to learn something new.
This is the User model. I have omitted a few fields due to the exact block of code being large, but this includes the "image" and "bio" fields (the fields I would like to update) as well as the reference to the Post model which I mentioned above that functions appropriately.
User.js
const userSchema = new Schema(
{
username: {
type: String,
required: true,
unique: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
match: [/.+#.+\..+/, 'Must match an email address!']
},
password: {
type: String,
required: true,
minlength: 8
},
image: {
type: String
},
bio: {
type: String,
maxLength: 500
},
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
],
},
Below is the mutation in Explorer, including the variables and the result.
Profile Pic Resolver
addProfilePic: async (parent, { image }, context) => {
if (context.user) {
const updatedUser = await User.findOneAndUpdate(
{ _id: context.user._id },
{ image: image },
{ new: true, runValidators: true }
);
return updatedUser;
}
throw new AuthenticationError('You need to be logged in!');
},
typeDefs.js (relevant only)
type Mutation {
addProfilePic(_id: ID!, image: String!): Auth
}
I notice that in the Explorer page it returns "null" for user with a 200 status. I am led to believe that means that it's not able to even access the "image" field on the user to be able to update it. When compared to my other mutations in regards to users, this is set up very similarly and I'm not sure what the difference is.
I feel like I am missing something very basic here in regards to being able to update. I haven't been able to find an update mutation example that works. Could anyone assist? My main questions would be:
Why does the mutation return "null" for user?
How can I set up my resolver to appropriately update information on an already-created object?
Thank you to anyone who is able to take a look and assist, I will be closely watching this post for replies and will update any other code someone may need to be able to assist. I've been stuck in regards to updating information for a long time, but my site is getting to the point where it's nearly ready and I need to tackle this updating issue in order to progress. Thank you!
Quick Edit: I want to add that "Auth" is referenced. The appropriate authorization headers are in place to retrieve the data. Just wanted to add that in as I highly doubt authorization has anything to do with this!
I have solved this issue and would like to leave the answer here for anyone who may find it useful.
In the mutation typeDefs, I changed the "Auth" to "User",
type Mutation {
addProfilePic(_id: ID!, image: String!): User
}
and then in the mutation itself, took away the user field like such:
mutation addProfilePic($_id: ID!, $image: String!) {
addProfilePic(_id: $_id, image: $image) {
_id
username
image
}
}
This has allowed the user to update their profile photo information. Hope this helps!

Posting to mongoDb with ObjectId Many to one relationship

Mongoose/MongoDB Question
I have an Owners model containing basic profile data.
I have a secondary model: OwnersImages
e.g
{
owner: {
type: Schema.Types.ObjectId,
ref: 'Owners'
},
name: String,
imageUrl: String,
},
);
From the client I want to post the imageUrl and the name to the OwnersImages table.
e.g
let values = {
owner: this.state.user._id,
name: this.state.field,
imageUrl: this.state.url
}
axios.post(`${serverPath}/api/addFieldImage`, values)
However Im unsure how best to go about this, link it etc.
I can do a GET request on the Owners table to get the Owner data, but then posting this as part of the values to OwnerImages doesn't successfully link the two tables.
Do i need to just store a string reference to the Owner id in OwnerImages or is there a smarter way of doing this?
Or should I just post the string of the user Id to mongoose and then do a map to the Owner table from within there?
Tried to explain this best way I could but the eyes are tired so please ask if any confusion!
Many thanks
Without seeing your exact setup, I think you could modify this to fit your needs:
// In the Schema/Model files
const ownersSchema = Schema({
// other fields above...
images: [{ type: Schema.Types.ObjectId, ref: 'OwnersImages' }]
});
const ownersImagesSchema = Schema({
// other fields above...
owner: { type: Schema.Types.ObjectId, ref: 'Owners' },
});
// in the route-handler
Owners.findById(req.body.owner, async (err, owner) => {
const ownersImage = new OwnersImages(req.body);
owner.images.push(ownersImage._id);
await ownersImage.save();
await owner.save();
});
As a side-note, I think the Models generally have singular names, so Owner and OwnerImage. The collection will then automatically take on the plural form. Just food for thought.
When you want to load these, you can link them with populate(). Consider loading all of the OwnersImages associated with an Owners in some route-handler where the /:id param is the Owners id:
Owners
.findOne({ _id: req.params.id })
.populate('images')
.exec(function (err, images) {
if (err) return handleError(err);
// do something with the images...
});

Handling Users with MongoDB Stitch App within Atlas Cluster

I have an MongoDB Stitch app, that users the Email/Password authentication. This creates users within the Stitch App that I can authenticate on the page. I also have an MongoDB Atlas Cluster for my database. In the cluster I have a DB with the name of the project, then a collection underneath that for 'Matches'. So when I insert the 'Matches' into the collection, I can send the authenticated user id from Stitch, so that I have a way to query all Matches for a particular User. But how can I add additional values to the 'User' collection in stitch? That user section is sort of prepackaged in Stitch with whatever authentication type you choose (email/password). But for my app I want to be able to store something like a 'MatchesWon' or 'GamePreference' field on the 'User' collection.
Should I create a collection for 'Users' the same way I did for 'Matches' in my Cluster and just insert the user id that is supplied from Stitch and handle the fields in that collection? Seems like I would be duplicating the User data, but I'm not sure I understand another way to do it. Still learning, I appreciate any feedback/advice.
There isn't currently a way to store your own data on the internal user objects. Instead, you can use authentication triggers to manage users. The following snippet is taken from these docs.
exports = function(authEvent){
// Only run if this event is for a newly created user.
if (authEvent.operationType !== "CREATE") { return }
// Get the internal `user` document
const { user } = authEvent;
const users = context.services.get("mongodb-atlas")
.db("myApplication")
.collection("users");
const isLinkedUser = user.identities.length > 1;
if (isLinkedUser) {
const { identities } = user;
return users.updateOne(
{ id: user.id },
{ $set: { identities } }
)
} else {
return users.insertOne({ _id: user.id, ...user })
.catch(console.error)
}
};
MongoDB innovates at a very fast pace - and while in 2019 there wasn't a way to do this elegantly, now there is. You can now enable custom user data on MongoDB realm! (https://docs.mongodb.com/realm/users/enable-custom-user-data/)
https://docs.mongodb.com/realm/sdk/node/advanced/access-custom-user-data
const user = context.user;
user.custom_data.primaryLanguage == "English";
--
{
id: '5f1f216e82df4a7979f9da93',
type: 'normal',
custom_data: {
_id: '5f20d083a37057d55edbdd57',
userID: '5f1f216e82df4a7979f9da93',
primaryLanguage: 'English',
},
data: { email: 'test#test.com' },
identities: [
{ id: '5f1f216e82df4a7979f9da90', provider_type: 'local-userpass' }
]
}
--
const customUserData = await user.refreshCustomData()
console.log(customUserData);

Resources