Next js with Prisma: Upsert based on two conditions - reactjs

I'm trying to do the following using Prisma:
If a category row with the same hash and user_id I have exists, just update it's "name" field, otherwise, create the row
Is this possible? TS is giving me an error saying that the type of the key given on "where" has to be of categoriesWhereUniqueInput, yet neither hash nor user_id are unique, they can repeat, it's the combination between the two that's gonna be unique
How can I work around this? Do I have to manually check if there's an id and update/create based on that?
Thanks a lot in advance!
const category = await prisma.categories.upsert({
where: {
hash,
user_id: id,
},
update: {
name,
},
create: {
hash,
name,
user_id: id,
},
});

When a combination of fields is unique, Prisma will generate a new type for the where condition which is basically just the name of all the relevant fields appended together with _.
Take a look at the categoriesWhereUniqueInput to see what the name is. Most likely it is called user_id_hash or hash_user_id.
This is what the query will look like, roughly speaking:
const category = await prisma.categories.upsert({
where: {
user_id_hash: { // user_id_hash is the type generated by Prisma. Might be called something else though.
user_id: 1,
hash: "foo"
}
},
update: {
name: "bar",
},
create: {
hash: "foo",
name: "bar",
user_id: 1,
},
})

Related

Search Flow in MERN Stack

First of all, I tried searching a lot but I am not able to find any resource which satisfies my need. I know there might be some answers already, if you know one please help with the link.
I know how to show search suggestions but I don't know how to show full search results when someone clicks on a search suggestion. Like how to do that in MERN stack with an example if possible.
I need a solution that best fits my scenario:
I have three models,
tags - holds tags
categories - holds categories
items - holds items data - has categories and tags both
currently, I am not storing references to categories and tags table instead
storing a copy directly inside items
Now, I basically want to search the items having the specific categories and tags when someone searches for a keyword.
What I am doing currently is, I search for tags matching the keyword, then categories, then taking out their _id(s) and finding that in items collection
const tags = await Tags.find(
{ tag: { $regex: category.toString(), $options: "i" } },
{ projection: { createdBy: 0 } });
const categories = await Categories.find(
{ category: { $regex: category.toString(), $options: "i" } },
{ projection: { createdBy: 0 } });
const tagsIdArray = tags.map((item) => new ObjectId(item._id));
const catIdArray = categories.map((item) => new
Object(item._id));
$match: {
$and: [
{
$or: [
{ "tags._id": { $in: [...tagsIdArray] } },
{ "category._id": { $in: [...catIdArray] } },
],},],},
And I know that this is not the best way, and it takes a lot of time to search for a given keyword.
Please suggest me schema structure and way to implement search with suggestions.

Work around Sequelize’s unique constraints in belongsToMany associations

I'm using Sequelize in my project. These are the two models:
const User = db.define('user', {
name: Sequelize.STRING,
password: Sequelize.STRING
})
const Product = db.define('product', {
name: Sequelize.STRING,
price: Sequelize.INTEGER
})
Now users can purchase products and I have associations setup like below:
Product.belongsToMany(User, {through: 'UserProducts'})
User.belongsToMany(Product, {through: 'UserProducts'})
I also have this UserProducts table with an additional column.
const UserProducts = db.define('UserProducts', {
status: Sequelize.STRING
})
Sequelize creates a composite key with combination of userId and productId and will only allow one record with a combination of userId and productId. So, for example, userId 2 and productId 14.
This is a problem for me because sometimes people want to purchase multiple times. I need one of the following scenarios to work:
Don't use the composite key and instead have a completely new auto-increment column used as key in UserProducts.
Instead of making key with userId and productId alone, allow me to add one more column into the key such as the status so that unique key is a combination of the three.
I do want to use the associations as they provide many powerful methods, but want to alter the unique key to work in such a way that I can add multiple rows with the same combination of user id and product id.
And since my models/database is already running, I will need to make use of migrations to make this change.
Any help around this is highly appreciated.
If anyone else is having problems in v5 of Sequelize, it is not enough to specify a primary key on the 'through' model.
You have to explicitly set the unique property on the through model.
User.belongsToMany(Product, { through: { model: UserProducts, unique: false } });
Product.belongsToMany(User, { through: { model: UserProducts, unique: false } });
Belongs-To-Many creates a unique key when the primary key is not present on through model.
Since you also have additional property in your UserProducts table, you need to define the model for UserProducts and then tell the sequelize to use that model instead of creating its own
class User extends Model {}
User.init({
name: Sequelize.STRING,
password: Sequelize.STRING
}, { sequelize })
class Product extends Model {}
ProjProductect.init({
name: Sequelize.STRING,
price: Sequelize.INTEGER
}, { sequelize })
class UserProducts extends Model {}
UserProducts.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
}, { sequelize })
User.belongsToMany(Project, { through: UserProducts })
Product.belongsToMany(User, { through: UserProducts })
refer: Sequelize v4 belongsToMany
UPDATE
since you are using v3 and already have a model created for your UserProducts table use following snippet
UserProducts = db.define('UserProducts', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
})
Since setting explicitly the unique property on the through model is not working in v6, the only solution i found is to define the 3 parts of the association this way :
User.hasMany(UserProducts);
UserProducts.belongsTo(User);
Product.hasMany(UserProducts);
UserProducts.belongsTo(Product);
You can then create your models and associations :
const user = await User.create(user_data);
const product = await Product.create(product_data);
const up = await UserProduct.create(up_data);
await up.setUser(user);
await up.setProduct(product);
If anyone has a better solution, I would be happy to know it

How to pass root parameters in the resolver function of a nested query?

I have a query of the following nature
Category1(name: $cat1){
Category2(secondName: $cat2){
secondName
}}
My schema is like so:
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
Category1: {
type: new GraphQLList(Category1Type),
args: { name },
resolve: resolveCategory1
}}
})
And then the Category1Type is defined as:
const Category1Type = new GraphQLObjectType({
name: 'Category1',
description: '<>',
fields: () => ({
name: { type: GraphQLString },
category2: {
type: new GraphQLList(CategoryType2),
args: { secondName },
resolve: resolveCategory2
}
})
});
For simplicity sake, assume category2 is like so:
const Category2Type = new GraphQLObjectType({
name: 'Category2',
description: '<>',
fields: () => ({
name: { type: GraphQLString },
})
});
Now I want to fetch all Category2 items under Category1 with option to filter, like so:
Category1(name: $name){
name
category2(name: $name){
name
}}
My resolvers are defined like so:
# Category1 resolver
function cat1resolve (root, args) {
return SELECT * from data WHERE category1_name = args.name
}
# Category2 resolver
function cat2Resolve (root, args) {
return SELECT * from data WHERE category1_name = rootargs.name and categort2_name = args.secondName }
Now the problem is that the 'resolver' for cat2Resolve is not able to see or receive the rootargs.name for me to do this kind of filtering.
The resolve function signature includes 4 parameters. From Apollo's docs:
obj: The object that contains the result returned from the resolver on the parent field, or, in the case of a top-level Query field, the
rootValue passed from the server configuration. This argument enables
the nested nature of GraphQL queries.
args: An object with the arguments passed into the field in the query. For example, if the field was called with author(name: "Ada"),
the args object would be: { "name": "Ada" }.
context: This is an object shared by all resolvers in a particular query, and is used to contain per-request state, including
authentication information, dataloader instances, and anything else
that should be taken into account when resolving the query. If you’re
using Apollo Server, read about how to set the context in the setup
documentation.
info: This argument should only be used in advanced cases, but it contains information about the execution state of the query, including
the field name, path to the field from the root, and more. It’s only
documented in the GraphQL.js source code.
Note: These docs are for graphql-tools' makeExecutableSchema (which I highly recommend) but the same applies to plain old GraphQL.JS.
The key point here is that a resolver for a particular field is generally agnostic to what other resolvers do or what information is passed to them. It's handed its own parent field value, its own arguments, the context and expected to work with that.
However, there is a workaround utilizing the info parameter. The object passed to info is huge and can be complicated to parse, but contains virtually all the information about the requested query itself. There are libraries out to help with parsing it, but you may want to print the whole thing to console and poke around (it's pretty cool!).
Using something like lodash's get, we can then do:
const category1id = get(info, 'operation.selectionSet.selections[0].arguments[0].value.value')
and utilize that value inside your query. The above is pretty fragile, since it assumes your request only contains the one query, and you only have one argument on the Category1 field. In practice, you'd probably want to utilize Array.find and look up the fields/arguments by name, but this should give you a starting point.

Mongoose - Remove several objects from an array (not exact match)

I have a collection Playlist that contains an array of items
{
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [
{
id: { // do not mix up with _id, which is the autogenerated id of the pair {id,type}. ID is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
}
]
}
Mongo automatically adds the _id field to the items when I push a pair {id,type} to items (but I don't care about it).
Now I would like to remove several "pairs" at once from the items array.
I have tried using $pullAll but it requires an exact match, and I do not know the _id, so it does not remove anything from items
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
I have tried using $pull with different variants, but it removed ALL objects from items
playlistModel.update({userId:userId},{$pull:{items:{"items.id":{$in:["123","456"]}}}},null,function(err){
playlistModel.update({userId:userId},{$pull:{items:{$in:[{id:"123",type:"video"},{id:"456",type:"video"}]}}},null,function(err){
Am I missing something or am I asking something that isn't implemented?
If the latter, is there a way I can go around that _id issue?
OK I found a way that works using $pull:
playlistModel.update({userId:userId},{$pull:{items:{id:{$in:["123","456"]}}}},null,function(err){
It doesn't take the type into account but I can't see any issue with that since the id is unique across all types anyway.
Although I will wait a bit to see if someone has a better solution to offer
EDIT
With Veeram's help I got to this other solution, which IMO is more elegant because I don't have _ids that I don't need in the database, and the $pullAll option seems more correct here
var playlistItemSchema = mongoose.Schema({
id: { // do not mix up with autogenerated _id. id is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
},{ _id : false });
var schema = new Schema({
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [playlistItemSchema]
});
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
tips:
you can use _id field to handle your playlistModel data.
mongoose api : new mongoose.Types.ObjectId to generate an Object_id
let _id=new mongoose.Types.ObjectId;
playlistModel.updateMany({_id:_id},{ $set: { name: 'bob' }}).exec(data=>{console.log('exec OK')});

mongoose query: find an object by id in an array

How could I find an image by id in this Schema. I have the id of the User and the id of the image I am looking for. What would be the best way to do this and do all images in this case have different ids or could they have the same id because they don't belong to the same User?
My Schema looks like this:
var userSchema = new Schema({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
name: String,
about: String,
images: [{
id: Schema.ObjectId,
link: String,
main: Boolean
}]
});
When you are interested in the full object it is a simple find:
.find({"facebook.id":"<id>", "images.id":<image-id>})
I don't think that there is a way to reduce the image array in the result.
To update a single element in the image array you can use this:
.update({"facebook.id":"<id>", "images.id":<image-id>}, {$set : {"images.$.main" :false} } );
userSchema .find({facebook.id: "some ID",{ "images.id": { $in: [ id1, id2, ...idn] }}
since images are inside the document you can have same ID's however every time you query you should keep in mind that you send some other parameters such as facebook.id or facebook.email along with image id's to retrieve them. Otherwise you end up getting all that might be irrelevant only because you decide to keep same ID's for images.
tl;dr
I struggled with this and came up with a solution. Like you, I was trying to query for a deeply nested object by the _id, but I kept coming up empty with the results. It wasn't until I did some type checking that I realized the id value I was getting from my frontend, while directly supplied by mongoose, was in fact a String and not an Object.
I realize this question was already partially answered before, but that person's solution didn't work for me, and the comment on the answer tells me you wanted to update the specific image you queried for, which is exactly what I was trying to do.
The solution
In order to select an object from the nested array by the _id value, first you'll have to install the npm package bson-objectid and use the provided method to convert your string into an objectId in your query.
In your terminal:
npm i bson-objectid
In your code:
const ObjectId = require('bson-objectid')
userSchema.findOneAndUpdate(
{ "facebook.id": <user-id>, "images._id": ObjectId(<image-id>) },
{ "$set": { "images.$.main": false } },
{ new: true }, // an extra options parameter that returns the mutated document
(err, user) => {
if (err) {
handleErr(err)
} else {
console.log(user)
// do something with new user info
}
)

Resources