How to use upsert with Prisma? - database

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.

Related

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

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

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

Meteor.users subscribe only return current user

I'm trying to get a user by the username, when I use Meteor.users.findOne, it always return the current user. And if I user Meteor.users.find, it returns all current user document, and the profile.firstName and profile.lastName of the right matched username.
Meteor.publish('userByUsername', function(username) {
return Meteor.users.findOne({
username: username
}, {
fields: {
'profile.firstName': 1,
'profile.lastName': 1,
}
});
});
How can I get only the user that match with the username?
I think what you want is not publish, but a method access to particular username. Publish/Subscribe is great for datasets that often change - say posts on stackoverflow, facebook feed, news articles, etc.
You are looking to get first/last name of a particular user, this does not really change. So what you actually want is to create a server method that returns the first/last name of the user. And you can call this method from the client to access this data.
if (Meteor.isClient) {
//get username var
Meteor.call('findUser', username, function(err, res) {
console.log(res.profile.firstName + " " + res.profile.lastName);
});
}
if (Meteor.isServer) {
Meteor.methods({
findUser: function(username) {
return Meteor.users.findOne({
username: username
}, {
fields: {
'profile.firstName': 1,
'profile.lastName': 1
}
});
}
});
}
Notice that the client Meteor.call has a callback method. DB queries on Meteor server is asynchronous & non-blocking, so you need to access the result via a javascript callback function.
findOne finds and returns the first document that matches the selector. Publish method needs to return a cursor, you need to use find, instead of findOne:
Meteor.publish('userByUsername', function(username) {
return Meteor.users.find({
username: username
}, {
fields: {
'profile.firstName': 1,
'profile.lastName': 1,
}
});
});
Then you can call subscribe on the client:
Meteor.subscribe('userByUsername', 'bob');
And call Meteor.users.findOne({ username: 'bob' }); in your helper for example.

Resources