How to delete from database where multiple conditions are met? - database

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 } });

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.

Prisma specific one to many relation

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.

Stripe webhook checkout.session items to Supabase

Im using next.js and Stripe webhooks to insert checkout sessions to Supabase that will create a customer's order history. I'm able to get the information about the whole order written to a table called 'orders', but am wondering what the best way to add individual items within each checkout session to another table called 'order_items' is. This way I can map through main orders and then the children items. Appreciate any help provided. Here is what I have for getting orders associated with a customer:
const upsertOrderRecord = async (session: Stripe.Checkout.Session, customerId: string) => {
const { data: customerData, error: noCustomerError } = await supabaseAdmin
.from<Customer>('customers')
.select('id')
.eq('stripe_customer_id', customerId)
.single();
if (noCustomerError) throw noCustomerError;
const { id: uuid } = customerData || {};
const sessionData: Session = {
id: session.id,
amount_total: session.amount_total ?? undefined,
user_id: uuid ?? undefined
};
const { error } = await supabaseAdmin.from<Session>('orders').insert([sessionData], { upsert: true });
if (error) throw error;
console.log(`Product inserted/updated: ${session.id}`);
};
The Checkout Session object contains a line_items field which is a list of each item included in the purchase.
However this field is not included in the object by default, and therefore won't be a part of your webhook payload. Instead you'll need to make an API call in your webhook handle to retrieve the Checkout Session object, passing the expand parameter to include the line_items field:
const session = await stripe.checkout.sessions.retrieve('cs_test_xxx', {
expand: ['line_items']
});

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...
});

AWS AppSync - Implement many to many connections using 1-M #connections and a joining #model

Following AWS documentation (https://aws-amplify.github.io/docs/cli-toolchain/graphql > Many-To-Many Connections), I try to understand the workaround example they provide for many to many connections (which seems not supported yet by Amplify).
The schema is:
type Post #model {
id: ID!
title: String!
editors: [PostEditor] #connection(name: "PostEditors")
}
# Create a join model and disable queries as you don't need them
# and can query through Post.editors and User.posts
type PostEditor #model(queries: null) {
id: ID!
post: Post! #connection(name: "PostEditors")
editor: User! #connection(name: "UserEditors")
}
type User #model {
id: ID!
username: String!
posts: [PostEditor] #connection(name: "UserEditors")
}
Using AWS AppSync Console, so far I'm able to:
Create a user using this mutation:
mutation {
createUser(input:{
username: "theUserName"
}){
username
}
}
Create a post using this mutation:
mutation {
createPost(input: {
title: "second post"
}){
title
}
}
But I don't understand how to add multiple editors to a post? So far, I'm able to add editors to a post using PostEditor join, but in their example, there is this statement (which I don't understand very well), so I don't think this is the good approach:
# Create a join model and disable queries as you don't need them
# and can query through Post.editors and User.posts
So I guess that using this join model to perform mutation is not what I have to do. Nevertheless, to be able to create this relation between a post and an editor, I created a mutation (retrieving "postEditorPostId" and "postEditorEditorId" from both previous mutations):
mutation {
createPostEditor(input:{
postEditorPostId: "XXX-XXX-XXX"
postEditorEditorId: "YYY-YYY-YYY"
}){
post {
title
}
editor {
username
posts {
items {
post {
title
}
}
}
}
}
}
Do I need to perform this previous mutation everytime I add a new editor (so the mutation will remain the same but "postEditorEditorId" will change? it seems obviously not a scalable approach, if for example the UI allows an admin to add 50 or more new editors (so it will need 50 mutations).
Finally I can get the information I need using this query:
query{
getUser(id: "YYY-YYY-YYY"){
username
posts {
items {
post {
title
}
}
}
}
}
Is there a better way (I suppose) to add editors to a post?
edit:
Using a promise, I am able to add multiple editors to a post, but it involves to execute as mutation as mutations as there are users:
const users = [{id: "U1", username: "user1"}, {id: "U2", username: "user2"}];
const post = { id: "P1", title: "Post 1" };
/*
After creating two users and a post using the approriate mutations
Using the CreatePost join below to make user1 and user2 editor on Post 1
*/
function graphqlCreatePostEditor(editorID) {
return new Promise((resolve, reject) => {
resolve(
API.graphql(graphqlOperation(createPostEditor, {
input: {
postID: post.id,
}
}))
)
})
}
let promises = users.map(user=> {
return graphqlCreatePostEditor(user.id)
.then(e => {
console.log(e)
return e;
})
});
Promise.all(promises)
.then(results => {
console.log(results)
})
.catch(e => {
console.error(e);
})
Still looking if there is a way to pass an array in a sigle mutation.
For simplicity sake, I'm lets go with a User model and a Project model where a user can have many projects and belong to many projects.
Note: The creation of join table as I've described it here is for the Amplify JS API for React / React Native / JavaScript
User model
type User #model {
id: ID!
username: String!
projects: [UserProject] #connection(name: "UserProject")
}
Project model
type Project #model {
id: ID!
project_title: String!
users: [UserProject] #connection(name: "ProjectUser")
}
Join table
type UserProject #model {
id: ID!
user: User #connection(name: "UserProject")
project: Project #connection(name: "ProjectUser")
}
Creation of Join table
Prerequisite: Fetch both user.id and project.id however you want to do that.
const UserProjectDetails = {
userProjectUserId: user.id
userProjectProjectId: project.id
};
API.graphql({ query: mutations.createUserProject, variables: {input: UserProjectDetails}})
And there you have it.
This article on dev.to was also pretty straight to the point:
https://dev.to/norrischebl/modeling-relationships-join-table-graphql-aws-amplify-appsync-1n5f

Resources