i'm currently working on a twitter clone with express / prisma / postgreSQL for my backend.
I'm struggling to imitate twitter's feed.
Here's my current prisma Schema
model User {
id String #id #default(cuid())
email String #unique
username String #unique
profilename String?
password String
bio String?
image String?
createdAt DateTime #default(now())
isAdmin Boolean #default(false)
tweets Tweet[]
retweets Retweet[]
likes Like[]
followers Follows[] #relation("follower")
following Follows[] #relation("following")
}
model Follows {
follower User #relation("following", fields: [followerId], references: [id])
followerId String
following User #relation("follower", fields: [followingId], references: [id])
followingId String
createdAt DateTime #default(now())
##id([followerId, followingId])
}
model Tweet {
id String #id #default(cuid())
content String
createdAt DateTime #default(now())
deleted Boolean #default(false)
media String[]
author User #relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
originalTweet Tweet? #relation("replies", fields: [originalTweetId], references: [id], onDelete: Cascade)
originalTweetId String?
responses Tweet[] #relation("replies")
likes Like[]
retweets Retweet[]
hashtags Hashtag[]
}
model Hashtag {
id String #id #default(cuid())
name String #unique
createdAt DateTime #default(now())
tweets Tweet[]
}
model Retweet {
id String #id #default(cuid())
tweet Tweet #relation(fields: [tweetId], references: [id], onDelete: Cascade)
tweetId String
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
createdAt DateTime #default(now())
}
model Like {
id String #id #default(cuid())
tweet Tweet #relation(fields: [tweetId], references: [id], onDelete: Cascade)
tweetId String
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
createdAt DateTime #default(now())
}
and my current function to get user feed
exports.getUserFeed = async (userId, options = {}) => {
try {
const user = await prisma.user.findUnique({
where: { id: userId },
include: { following: true },
})
if (user.following.length === 0) {
return prisma.tweet.findMany({
orderBy: {
createdAt: "desc",
},
...options,
})
}
return prisma.tweet.findMany({
where: {
OR: [
{
author: {
followers: {
some: {
followerId: userId,
},
},
},
},
{
likes: {
some: {
user: {
followers: {
some: {
followerId: userId,
},
},
},
},
},
},
{
retweets: {
some: {
user: {
followers: {
some: {
followerId: userId,
},
},
},
},
},
},
],
},
orderBy: {
createdAt: "desc",
},
...options,
})
} catch (err) {
throw new Error(err)
}
}
With this i get tweets posted by user i follow, liked by user i follow and retweeted by user i follow but order by tweet's created at not by the "event" created at.
To solve this i thought about creating a new model called Event or Status with optionnal feed like:
isRetweet ?
isLike ?
etc
and then retrieve this instead of tweet model.
But it seems messy and complex for no reason.
Is there a way to actually define a better schema or do a better query ?
Thanks a lot.
Related
Hi I have three models
model User {
user_id Int #id #default(autoincrement())
email String #unique
name String?
User_Account User_Account[]
}
model Account {
account_id Int #id #default (autoincrement()) #unique
email String
bank String
createdAt DateTime #default(now())
User_Account User_Account[]
}
model User_Account {
id Int #id #default(autoincrement())
accountId Int
userId Int
User User #relation(fields: [userId], references: [user_id])
Account Account #relation(fields: [accountId], references: [account_id])
}
I am trying to seed my db like this
const data = [
{
id: 1,
email: 'pranit1#mf.com',
name: 'Pranit1',
bank: 'VCB',
ids: [1,1]
},
{
id: 2,
email: 'pranit1#mf.com',
name: 'Pranit1',
bank: 'ACB',
ids: [1,2]
},
{
id: 3,
email: 'pranit3#mf.com',
name: 'Pranit3',
bank: 'VCB',
ids: [2,3]
}
]
const users = await prisma.$transaction(
data.map(user =>
prisma.user.upsert({
where: { email: user.email },
update: {},
create: { name: user.name,
email:user.email },
})
)
);
const accounts = await prisma.$transaction(
data.map(account =>
prisma.account.upsert({
where: { account_id: account.id },
update: {},
create: { bank: account.bank ,
email :account.email },
})
)
);
const user_accounts = await prisma.$transaction(
data.map(uacc =>{
console.log(uacc);
return prisma.user_Account.upsert({
where: { id: uacc.id },
update: {id: uacc.id},
create:{
userId: uacc.ids[0],
accountId: uacc.ids[1] },
})}
)
);
However I am getting an
Unique constraint failed on the constraint: User_Account_userId_key
The data in prisma studio is generated as shown in the image
I am simply trying to create users and accounts and a user can be associated with multiple accounts. Their relation is shown in the User_Account table. I cant see why I am getting a unique constraint error when I dont have the #unique tag on userId
I was unable to reproduce the error on my side.
But I suspect you already had records on the DB and they conflict with the ids of your seeder.
Also, there are some improvements you could make on your schema for being simpler.
Since you don't have any extra details on the many-to-many relation you can get rid of the User_Account model and let Prisma handle it for you.
On the seeder, you can take advantage of the nesting features of Prisma so that you don't have to manually link the records. That way, you don't have to worry about ids.
schema.prisma suggestion
model User {
id Int #id #default(autoincrement())
email String #unique
name String?
accounts Account[]
}
model Account {
id Int #id #unique #default(autoincrement())
email String
bank String
users User[]
createdAt DateTime #default(now())
}
seed.js suggestion
const { PrismaClient } = require("#prisma/client");
const prisma = new PrismaClient();
async function main() {
const usersData = [
{
email: "pranit1#mf.com",
name: "Pranit1",
banks: ["VCB", "ACB"],
},
{
email: "pranit3#mf.com",
name: "Pranit3",
banks: ["VCB"],
},
];
const users = await prisma.$transaction(
usersData.map((user) =>
prisma.user.upsert({
where: { email: user.email },
update: {},
create: {
name: user.name,
email: user.email,
accounts: {
create: user.banks.map((bank) => ({
email: user.email,
bank,
})),
},
},
})
)
);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
I want to find only one post matching by post id and prodCode
below is my query code. It doesn't work.
If i change findUnique to findFirst. it works.
const post = await client.post.findUnique({
where: {
AND: [
{ id: postId },
{
product: {
prodCode,
},
},
],
},
});
prisma model
model Product {
id Int #id #default(autoincrement())
prodName String
prodCode String #unique
posts Post[]
holdings Holding[]
proposes Propose[]
}
model Post {
id Int #id #default(autoincrement())
user User #relation(fields: [userId], references: [id])
userId Int
product Product #relation(fields: [productId], references: [id])
productId Int
title String
content String
createdAt DateTime #default(now())
}
Since Post.id is unique, you don't need to filter by prodCode as well. You could just query the post record with the needed id and then check if the connected product has the right prodCode.
I would just do this:
const post = await prisma.post.findUnique({
where: {
id: postId
},
include: {
product: true
}
});
if (post.product.prodCode === prodCode) {
// No result for desired query
} else {
// "post" variable contains result of desired query
}
I am wondering if it is possible to pass multiple IDs to a useQuery for apollo hook. Or run a single query per ID, and if so how would I go about doing so.
I have the following query
const DECOR_SHEET = gql`
query GetDecorSheet($id: ID!) {
findDecorSheetByID(id:$id){
refIdArray
sheetName
_id
}
}
`;
and the following useQuery hook
const { loading, error, data: sheetData } = useQuery(DECOR_SHEET, {
variables: { id: id },
context: {
headers: {
authorization: cookieBearer,
},
},
});
I have the following IDs 293164663883956749, 293526016787218952 and I would like to return into one object in order to render into a component.
I am using Fauna DB and this is the input graphQL schema
type Catalog {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: User
}
type Decor {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: User!
visible: Boolean
}
type DecorSheet {
sheetName: String
refIdArray: String
owner: User!
}
type User {
email: String! #unique
catalog: Catalog
decor: [Decor!] #relation
decorSheet: [DecorSheet!] #relation
}
and this is the generated schema
directive #embedded on OBJECT
directive #collection(name: String!) on OBJECT
directive #index(name: String!) on FIELD_DEFINITION
directive #resolver(
name: String
paginated: Boolean! = false
) on FIELD_DEFINITION
directive #relation(name: String) on FIELD_DEFINITION
directive #unique(index: String) on FIELD_DEFINITION
type Catalog {
_id: ID!
decor: Boolean
clothing: Boolean
supplies: Boolean
owner: User
furniture: Boolean
_ts: Long!
}
input CatalogInput {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: CatalogOwnerRelation
}
input CatalogOwnerRelation {
create: UserInput
connect: ID
disconnect: Boolean
}
scalar Date
type Decor {
purchaseDate: Date
visible: Boolean
image: String
description: String
_id: ID!
alterations: Boolean
cost: Int
pieces: Int
category: String
owner: User!
purchaser: String
itemNum: Int
_ts: Long!
}
input DecorInput {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: DecorOwnerRelation
visible: Boolean
}
input DecorOwnerRelation {
create: UserInput
connect: ID
}
type DecorPage {
data: [Decor]!
after: String
before: String
}
type DecorSheet {
refIdArray: String
_id: ID!
sheetName: String
owner: User!
_ts: Long!
}
input DecorSheetInput {
sheetName: String
refIdArray: String
owner: DecorSheetOwnerRelation
}
input DecorSheetOwnerRelation {
create: UserInput
connect: ID
}
type DecorSheetPage {
data: [DecorSheet]!
after: String
before: String
}
scalar Long
type Mutation {
updateUser(
id: ID!
data: UserInput!
): User
createUser(data: UserInput!): User!
createDecorSheet(data: DecorSheetInput!): DecorSheet!
createDecor(data: DecorInput!): Decor!
deleteCatalog(id: ID!): Catalog
updateCatalog(
id: ID!
data: CatalogInput!
): Catalog
updateDecor(
id: ID!
data: DecorInput!
): Decor
updateDecorSheet(
id: ID!
data: DecorSheetInput!
): DecorSheet
deleteDecor(id: ID!): Decor
deleteUser(id: ID!): User
createCatalog(data: CatalogInput!): Catalog!
deleteDecorSheet(id: ID!): DecorSheet
}
type Query {
findUserByID(id: ID!): User
findCatalogByID(id: ID!): Catalog
findDecorByID(id: ID!): Decor
findDecorSheetByID(id: ID!): DecorSheet
}
scalar Time
type User {
catalog: Catalog
email: String!
_id: ID!
decor(
_size: Int
_cursor: String
): DecorPage!
decorSheet(
_size: Int
_cursor: String
): DecorSheetPage!
_ts: Long!
}
input UserCatalogRelation {
create: CatalogInput
connect: ID
disconnect: Boolean
}
input UserDecorRelation {
create: [DecorInput]
connect: [ID]
disconnect: [ID]
}
input UserDecorSheetRelation {
create: [DecorSheetInput]
connect: [ID]
disconnect: [ID]
}
input UserInput {
email: String!
catalog: UserCatalogRelation
decor: UserDecorRelation
decorSheet: UserDecorSheetRelation
}
There is an option to query with Fauna's FQL which may have a way of querying multiple IDs I will have to look into that, but would prefer to do this in graphQL with apollo if possible.
Thanks ahead of time
Thanks to support from FaunaDB
The following query and UDF does the trick
Query
type Query {
getMultipleDecors(DecorId: [ID!]): [Decor]
#resolver(name: "get_multiple_decors")
}
udf named get_multiple_decors
Query(
Lambda(
["input"],
Let(
{
data: Map(
Var("input"),
Lambda("x", Get(Ref(Collection("Decor"), Var("x"))))
)
},
Var("data")
)
)
)
If it's always exactly two ids, you can fetch both objects in a single query easily using field aliases:
const DECOR_SHEET = gql`
query GetDecorSheet($firstId: ID!, $secondId: ID!) {
firstDecorSheet: findDecorSheetByID(id: $firstId) {
refIdArray
sheetName
_id
}
secondDecorSheet: findDecorSheetByID(id: $secondId) {
refIdArray
sheetName
_id
}
}
`;
I couldn't find any relevant info on Prisma docs, nor on SO on this question.
My relevant schema looks like this:
model User {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
firstName String?
lastName String?
email String #unique
hashedPassword String?
role String #default("user")
sessions Session[]
profile Profile?
}
model Profile {
id Int #default(autoincrement()) #id
aboutMe String?
location String?
profession String?
user User #relation(fields:[userId], references: [id])
userId Int
}
I want to update multiple columns in both tables however, have been unable to get the mutation to work in Prisma. So, this is what my mutation looks like at the moment:
...
const {aboutMe, location, profession, firstName, lastName } = inputs;
const profile = await db.user.update({
where: { id: ctx.session!.userId },
data: {
profile: {
update: {
aboutMe,
location,
profession,
},
},
},
});
const user = await db.user.update({
where: { id: ctx.session!.userId },
data: {
firstName,
lastName,
},
});
...
As you can see, I have two mutations, is it possible the update multiple tables with a single mutation?
Thank you.
This should work:
const profile = await db.user.update({
where: { id: u.id },
data: {
firstName: 'name',
lastName: 'name',
profile: {
update: {
aboutMe: 'aboutMe',
location: 'aboutMe',
profession: 'aboutMe',
},
},
},
include: { profile: true },
})
I am creating an ecommerce api using GraphQL. My prisma schema is
model Product {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
name String
price Int
stocks Int
}
model User {
id Int #id #default(autoincrement())
name String
email String #unique
password String
Orders Order[]
}
model Order{
id Int #id #default(autoincrement())
product Product #relation(fields: [productId], references: [id])
productId Int
user User #relation(fields: [userId], references: [id])
userId Int
##unique([productId, userId])
}
What should be my graphql schema and resolver function in order to create a relation whenever a user orders a product?
Your schema should actually look like this:
model Product {
id Int #id #default(autoincrement())
name String
price Int
stocks Int
orders Order[]
createdAt DateTime #default(now())
}
model User {
id Int #id #default(autoincrement())
name String
email String #unique
password String
orders Order[]
}
model Order {
id Int #id #default(autoincrement())
product Product #relation(fields: [productId], references: [id])
productId Int
user User #relation(fields: [userId], references: [id])
userId Int
##unique([productId, userId])
}
And your GraphQL mutation should contain the user id and product id being passed as variables and your resolver would look like this:
await prisma.user.update({
where: {
id: 1,
},
data: {
orders: {
create: {
product: {
connect: {
id: 123,
},
},
},
},
},
})
This will create a new order to the user id 1 and will add product id 123 to the order.