Breaking down complex Prisma queries in Next.js app - reactjs

I’ve got a Next.JS app. I want to render a todo feed on the homepage, but also the user page. I'm a bit stuck on how to break down my Prisma queries.
I fetch a big data object using getServerSideProps and pass this to the page component (and using react-query to hydrate and do re-fetching, but not relevant now)
- getRecentTodos (includes todos) for my homepage
- getUserDetailsByName (includes todos) for the user page
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: {
id: true,
userId: true,
content: true,
done: true,
createdAt: true,
attachments: true,
todoReplies: {
select: {
id: true,
userId: true,
content: true,
todoReplyLikes: true,
todoId: true,
user: { select: { name: true, displayName: true, image: true } },
},
orderBy: { createdAt: 'asc' },
},
todoLikes: {
select: {
user: true,
},
},
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
},
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: {
name: true,
displayName: true,
bio: true,
location: true,
twitter: true,
image: true,
createdAt: true,
todos: {
select: {
id: true,
content: true,
userId: true,
done: true,
updatedAt: true,
createdAt: true,
attachments: true,
user: true,
todoLikes: true,
todoReplies: {
take: 30,
orderBy: { createdAt: 'desc' },
select: {
id: true,
userId: true,
todoId: true,
createdAt: true,
content: true,
user: true,
},
},
},
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
},
})
}
Both queries return ‘todos,’ but they can return it in a slightly different way. The todo feed component expects certain properties to be available
- E.g. displayName on todoReplies
- But on getUserDetailsByName the displayName might not be part of the response or it’s nested one layer deeper or something
How to keep this from getting complex very fast?
You more or less want to select todos in your queries the same way (returning the same and omitting the same, apart of some things like order)
But manually keeping these things in sync over lot’s of queries qet’s complex quickly
Possible solutions?
Should I break the getServerSideProps into multiple fetches?
So instead of one ‘getUserDetailsByName’ which has todos as a relationfield included
fetch user details
fetch todos
This would mean I also have to write more react-query code for refetching etc… because you are dealing with multiple objects. But it does seperate concerns more.
Using Typescript to catch it in my codebase when a function tries to access a property which is not returned from that specific Prisma query? (I’m just now starting to see the possibilities of Typescript for stuff like this)
Should I just standardize the way the todos get created in a prisma query with a function and include that function inside of the Prisma queries? you can include like:
const todoSelect = {
id: true,
userId: true,
content: true,
{.......}
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
}
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: todoSelect,
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
const userSelect = {
name: true,
{......}
todos: {
select: todoSelect,
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: userSelect,
})
}

Related

Failed to save data into postgres database using sequelize.js, but system return column multiple times

While trying to save data into postgres database using sequelize BlogModel.create() system failed to save the data into table and server is returning columns createdat, updatedat, multiple times in console. ( please see below ). In the scheme I have added the column only once, can someone advise on this issue here ?
Executing (default): INSERT INTO "userBlogs" ("id","email","blogdetails","tags","createdat","updatedat","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7) RETURNING "id","email","blogdetails","tags","createdat","updatedat","createdAt","updatedAt";
//userBlogs.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const userBlogs = sequelize.define('userBlogs', {
id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
blogdetails: {
type: DataTypes.TEXT,
allowNull: false
},
tags: {
type: DataTypes.STRING(255),
allowNull: false
},
createdat: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedat: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
timestamps: true,
tableName: 'userBlogs'
});
return userBlogs;
};
//server.js
const usersBlogSchema = require('./modals/userBlogs');
const BlogModel = usersBlogSchema(sequelize, DataTypes);
app.post('/service/createblogs', async (req, res, next)=> {
try {
const userEmail = req.body.email;
const blogDetails = req.body.blogValue;
const tags = req.body.tagValue;
if (Object.keys(req.body).length === 0) {
res.status(403).json({ fail: "Invalid blog request or blog request is blank !" });
} else {
var requestData = {email:userEmail, blogdetails:blogDetails, tags:tags };
const createBlogRequest = await BlogModel.create(requestData);
res.status(200).json({ success: true });
}
} catch (e) {
console.log(e)
return next(e);
}
});
Returning createdAt and updatedAt multiple times because you have added columns (createdAt and updatedAt )and also timestamps:true ,
timestamps also adds these both columns
use either columns or timestamps
'use strict';
module.exports = (sequelize, DataTypes) => {
const userBlogs = sequelize.define('userBlogs', {
id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
blogdetails: {
type: DataTypes.TEXT,
allowNull: false
},
tags: {
type: DataTypes.STRING(255),
allowNull: false
},
}, {
timestamps: true,
tableName: 'userBlogs'
});
return userBlogs;
};

I am having difficulty accessing this nested array to carry out an update

SCHEMA
Below is my schema structure, kindly correct me if I am getting it wrong. I want to be able to update the ConnectState from false to true using an ObjectId
phones: {
type: String,
required: true,
},
User: {
type: mongoose.Schema.Types.ObjectId,
ref: "user",
// required: true,
},
Userpost: {
type: mongoose.Schema.Types.ObjectId,
ref: "userpost",
// required: true,
},
friendshipStatus: [
{
isFriend: {
FProfile: {
type: mongoose.Schema.Types.ObjectId,
ref: "profile",
},
ConnectStatus: {
type: Boolean,
default: false,
},
},
},
],
});
What I have tried
I want to update the Boolean value on ConnectStatus from false to true. I know I am getting the process wrong.
const result = await Profile.updateOne(
{ "friendshipStatus.isFriend.FProfile": uid },
{ $set: { "friendshipStatus.$.isFriend.ConnectStatus": true } },
{ arrayFilters: [{ "friendshipStatus.isFriend.FProfile": uid }] }
);
Try with:
const result = await Profile.update(
{ 'friendshipStatus.isFriend.FProfile': uid },
{ $set: { 'friendshipStatus.$.isFriend.ConnectStatus': true } },
);

setState nested object in a map function

My State Object is a array that holds objects.PS:(Feel free to change the structure) Here is the structure:
{
type: ListOfTypes[Math.floor(Math.random() * ListOfTypes.length)],
name: ListOfNames[Math.floor(Math.random() * ListOfNames.length)],
id: nanoid(),
channels: [
{
id: nanoid(),
Name: ListOfChannels[Math.floor(Math.random() * ListOfChannels.length)],
Files: [ { folder: "Folder", documents: [ { doc: "WordDoc1.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc2.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc3.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc4.doc", isChecked: false, id:nanoid() }] }, ],
},
{
id: nanoid(),
Name: ListOfChannels[Math.floor(Math.random() * ListOfChannels.length)],
Files: [{ folder: "Folder", documents: [ { doc: "WordDoc1.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc2.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc3.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc4.doc", isChecked: false, id:nanoid() }] }, ],
},
{
id: nanoid(),
Name: ListOfChannels[Math.floor(Math.random() * ListOfChannels.length)],
Files: [{ folder: "Folder", documents: [ { doc: "WordDoc1.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc2.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc3.doc", isChecked: false, id:nanoid() }, { doc: "WordDoc4.doc", isChecked: false, id:nanoid() }] }, ],
}
]
}
I want to change all the isChecked in every channel object, currently I'm using this function but it doesn't work as intended.
const handleChange = () => {
const ConnectionsArray = List.map( (connection) => connection.id == connectionId ?
{
...connection,
channels: connection.channels.map( (channel) => channel.Name == channelName ? {
...channel,
Files: channel.Files.map((file) => ({
...file,
documents: file.documents.map((doc) => ({ ...doc, isChecked: !doc.isChecked }) )
}))
} : channel)
} : connection)
setList(ConnectionsArray)
};
This should do it:
function toggleChecked (connections) {
return connections.map(connection => (connection.id === connectionId
? {
...connection,
channels: connection.channels.map(channel => (channel.Name === channelName
? {
...channel,
Files: channel.Files.map(file => ({
...file,
documents: file.documents.map(doc => ({
...doc,
isChecked: !doc.isChecked,
})),
})),
}
: channel)),
}
: connection));
}
Use like this:
setList(list => toggleChecked(list));
Here's another function to help with getting a random item from an array (I notice you're repeating a lot math expressions to do this in your code):
function getRandomElement (array) {
return array[Math.floor(Math.random() * array.length)];
}
Use like this:
// before
ListOfTypes[Math.floor(Math.random() * ListOfTypes.length)]
// after
getRandomElement(ListOfTypes)
Probably a good time to learn how to use the "immer" library. It's perfect for situations like these where you need to make changes too deeply nested objects. Without it, making changes to objects like the one you're dealing with gets really messy and hard to deal with.
Immer is really easy to pick up and learn in a day or two. If you used it, your code could be reduced to this:
import produce from 'immer';
const handleChange = ()=>{
const ConnectionsArray = produce(List, draft=>{
draft.forEach((object)=>{
object.channels.forEach((channel)=>{
channel.Files.forEach((file)=>{
file.documents.forEach((document)=>{
document.isChecked = !document.isChecked;
})
})
})
})
})
}
I didn't run this code so not 100% sure it works. Either way, something like this with immer will work and be a lot easier to deal with. Notice you don't have to deal with the spread syntax or any of that, and immer will actually be creating a new object so it avoids any of the headaches associated with mutable data.
Check this:
const handleChange = () => {
setList(prevState => {
let ConnectionsArray = [...prevState];
const itemIndex = ConnectionsArray.find(item => item.id === connectionId);
const connection = {...ConnectionsArray[itemIndex]};
ConnectionsArray[itemIndex] = {
...connection,
channels: connection.channels.map( (channel) => channel.Name == channelName ? {
...channel,
Files: channel.Files.map((file) => ({
...file,
documents: file.documents.map((doc) => ({ ...doc, isChecked: !doc.isChecked }) )
}))
} : channel)
};
return ConnectionsArray;
})
};

Apollo GraphQl model attribute is not accessing object in ReactJs

I am implementing category and subcategory display in ReactJs using Apollo GraphQl Query.
I tried to using same table as category with fields.
id,
category_name,
category_img,
category_parent_id ( id from same table),
category_status,
typeDefs and resolver are belows
Category.js
const typeDefs = gql`
extend type Query {
getSingleCategory(id: ID): allCategory
}
`;
type allCategory {
id: ID!
category_name: String
category_img: String
category_parent_id: Int
category_status: Status
}
const resolvers = {
Query: {
getSingleCategory: async (parent, args, context, info) => {
var data = await db.category.findOne({
where: {
id: args.id,
},
include: [
{
model: db.category,
as: "children",
attributes: [["category_name", "children_name"]],
nested: true,
required: false,
},
],
required: false,
});
return data;
},
},
},
Model in GraphQl
module.exports = function (sequelize, DataTypes) {
var category = sequelize.define(
"category",
{
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
category_name: {
type: DataTypes.STRING(256),
allowNull: false,
},
category_img: {
type: DataTypes.STRING(256),
allowNull: false,
},
category_parent_id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
references: {
// WorkingDays hasMany Users n:n
model: "category",
key: "children",
},
},
category_status: {
type: DataTypes.ENUM("Acitve", "Inactive"),
allowNull: false,
},
},
{
tableName: "category",
timestamps: false,
}
);
category.associate = function (models) {
models.category.belongsTo(models.category, {
onDelete: "CASCADE",
foreignKey: "category_parent_id",
as: "children",
targetKey: "id",
});
};
return category;
};
In ReactJs
category.ts
export const GET_CATEGORYBY_ID = gql`
query($catId: ID!) {
getSingleCategory(id: $catId) {
id
category_name
category_img
category_parent_id
category_status
}
}
`;
I am trying to accessing {data.getSingleCategory} , I got all parameters but not able to get children_name from same table as parent_name.
Anyone can tell me what is the issue I am not able to access that children_name as attribute from same table Or there any other way so that we can access category/subcategory from same table and display it to reactjs template.
Not defined [separately] in types, not used/defined [as 'children' property?] in parent type, not requested in query ... simply filtered out from response.

Node.JS sequelizejs many to many

I'm working on a web site where users can have a project, and for each project they have, they are assigned a certain role. Here is the schema
How I can make it with the model of squelize (Or maybe it's better to don't use an ORM (it's what I'm thinking right now..))
Here you can see a part of my model :
//Users
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
// ******** //
}, {
tableName: 'users',
freezeTableName: true
});
};
//Projects
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Project', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'projects',
freezeTableName: true
});
};
//ProjectRole :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ProjectRole', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'projects_roles',
freezeTableName: true
});
};
//user has projects :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserProject', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
}, {
tableName: 'users_has_projects',
freezeTableName: true
});
};

Resources