I my schema.prisma file I have those 2 models:
model User {
id String #id #default(uuid()) #db.Uuid
firstName String #map("first_name") #db.VarChar(40)
lastName String #map("last_name") #db.VarChar(40)
email String #unique #db.Citext
phone String #db.VarChar(60)
dialCode String #map("dial_code") #db.VarChar(5)
password String #db.VarChar(500)
twoFactorSecret String #db.VarChar(300)
roleId String #map("role_id") #db.Uuid
role Role #relation(fields: [roleId], references: [id])
customPermissions UserCustomPermission[]
updatedAt DateTime #default(now()) #updatedAt #map("updated_at") #db.Timestamptz(6)
createdAt DateTime #default(now()) #map("created_at") #db.Timestamptz(6)
##unique([dialCode, phone], map: "phone_number_unique")
##map("user")
UserAction UserAction[]
}
model UserAction {
id String #id #default(uuid()) #db.Uuid
userId String #map("user_id") #db.Uuid
method String #db.VarChar(120)
url String #db.VarChar(120)
userAgent String #db.VarChar(120)
requestBody Json?
responseBody Json?
user User #relation(fields: [userId], references: [id])
updatedAt DateTime #default(now()) #updatedAt #map("updated_at") #db.Timestamptz(6)
createdAt DateTime #default(now()) #map("created_at") #db.Timestamptz(6)
}
It is supposed to be one-to-many connection, but I have this warning in terminal when trigger function that creates record:
async createRecord({userId, method, url, userAgent, requestBody, responseBody}) {
return this.prisma.userAction.create({
data: {userId, method, url, userAgent, requestBody, responseBody}
})
}
And this warning:
+ user: {
+ create?: UserCreateWithoutUserActionInput | UserUncheckedCreateWithoutUserActionInput,
+ connectOrCreate?: UserCreateOrConnectWithoutUserActionInput,
+ connect?: UserWhereUniqueInput
+ },
? id?: String,
? updatedAt?: DateTime,
? createdAt?: DateTime
}
}
Argument user for data.user is missing.
Note: Lines with + are required, lines with ? are optional.
Actually, everything seems to be working fine, but I am wondering on how to fix this. I guess, that connection between 2 tables was created incorrect, but I have never worked with prisma before, so I don't really know where error is.
So, how can I fix this? Thanks in advance!
I faced this error because I passed an undefined value to a foreign key field of one of my models.
In your case, should be checked that createRecord is being invoked with a valid userId.
Related
I'm using NextAuth and EmailProvider to handle log-in to a Next.js app, and new users are saved in a postgres database using Prisma. The schema includes a isAdmin field that defaults to false
I'd like to set that field to true for new users who sign in from a specific email domain but can't work out if that's possible - at the moment I need to go into the dB and set the flag manually once the user has signed up, which is obviously very much not ideal.
I'd very much appreciate any pointers, or even confirmation that what I'm trying to do is impossible!
ETA: I now think I may need to add either an Event or a Callback to the NextAuth function, but the documentation focuses on redirecting users rather than on user creation, so I'm still not sure if what I'm trying to do is even possible (https://next-auth.js.org/configuration/events).
Below is my pages>api>auth>[...nextauth].js code.
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
import { PrismaAdapter } from "#next-auth/prisma-adapter"
import prisma from "lib/prisma"
let data
if (process.env.NODE_ENV === "development") {
data = {
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}
} else {
data = {
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}
}
export default NextAuth({
providers: [EmailProvider(data)],
database: process.env.DATABASE_URL,
secret: process.env.SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60, // 30 days
},
debug: true,
adapter: PrismaAdapter(prisma),
callbacks: {
session: async ({ session, user }) => {
session.user.id = user.id
session.user.isAdmin = user.isAdmin
return Promise.resolve(session)
},
},
})
and my Prisma schema
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String #id #default(cuid())
name String?
email String? #unique
emailVerified DateTime?
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
isAdmin Boolean #default(false)
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String #unique
expires DateTime
##unique([identifier, token])
}
model Account {
id String #id #default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? #db.Text
access_token String? #db.Text
expires_at Int?
token_type String?
scope String?
id_token String? #db.Text
session_state String?
oauth_token_secret String?
oauth_token String?
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
##unique([provider, providerAccountId])
}
model Session {
id String #id #default(cuid())
sessionToken String #unique
userId String
expires DateTime
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
}
I have solved my own problem and the answer is "no ...but". If it is possible to hijack the next-auth user creation process, I haven't found out how - but I now suspect that it would involve forking the next-auth code and would all round be a Really Bad Idea anyway. So "no, you can't assign a role to a user at the point of creation"
However, as I mentioned in my edit to my question, next-auth has the concept of Events, which allow you to execute your own code in response to next-auth life-cycle occurrences e.g. the signIn event is triggered every time a successful log in occurs.
So I used the signIn event, which receives the user object and an isNewUser boolean as params. I check if the user is newly-created and if so, do they have the specified email domain? If yes, then I update the database after the user has been created.
Since this is all done during sign in, it looks like a single-step process to the user.
In detail:
(1) add the required email domain to the .env file:
ADMIN_EMAIL_DOMAIN="whitelist.domain.com"
(2) add an event to the NextAuth function in [...nextauth].js:
//...code as above
import { updateUserToAdmin } from "lib/data.js"
//...code as above
export default NextAuth({
//...code as above
events: {
signIn: async ({ user, isNewUser }) => {
if (isNewUser) {
const userEmail = user.email
const isAdminEmail =
userEmail.split("#")[1] === process.env.ADMIN_EMAIL_DOMAIN
isAdminEmail
? await updateUserToAdmin(user.id, prisma, isAdminEmail)
: console.log(`non-Admin domain`)
}
},
},
})
(3) add the updateUserToAdmin query to lib>data.js:
return await prisma.user.update({
where: {
id: userId,
},
data: {
isAdmin: isAdminEmail,
},
})
}
I hope someone else finds this helpful. Do feel free to comment if you want to suggest any improvements.
I have a model called "Setup"
model Setup {
id String #id #default(auto()) #map("_id") #db.ObjectId
userId String? #unique #db.ObjectId
user User? #relation(fields: [userId], references: [id])
contract String[]
legal String[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
In this model i want to store an array like
const contractData = {
id: '729a4839f3dapob44zt2b4b1',
name: 'Example Name',
text: 'Example Text'
}
so in my above model "Setup" i want to store the contractData
prisma.setup.create({
data: {
userId: '6399bc74426f71f2da6e316c',
personal: [],
contract: contractData,
legal: []
}
})
Unfortunately, this not work.
How can i define an Object for contract and store this in my database?
If you want to store a raw JSON, check out this guide: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields
You will want to use the Json in Prisma in order to be able to store a raw JSON object (or multiple JSON objects as an array.)
model Setup {
id String #id #default(auto()) #map("_id") #db.ObjectId
userId String? #unique #db.ObjectId
user User? #relation(fields: [userId], references: [id])
contract Json[]
legal String[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
Your Prisma prisma.setup.create query would basically be the exact same. Note that querying for what's in this JSON will be trickier - I would recommend creating a new model and then connecting it to the Setup model, but that if that isn't an option you can still perform some limited queries on the contract field.
Docs: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filter-on-a-json-field
In order to store an object in the contract field, you will need to change the type of the contract field from String[] to Object[].
model Setup {
id String #id #default(auto()) #map("_id") #db.ObjectId
userId String? #unique #db.ObjectId
user User? #relation(fields: [userId], references: [id])
contract Object[]
legal String[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
store an object in the contract field like
prisma.setup.create({
data: {
userId: '6399bc74426f71f2da6e316c',
personal: [],
contract: [contractData],
legal: []
}
})
contract field is now an array, so you will need to pass the object as an element of the array, like [contractData]
I´m doing an Spotify clone and I´m trying to add a song to a playlist but my query doesn't work, until this point, everything was good following the documentation on prisma docs, but I cannot do this query, every time get an error, so if someone can tell me, how can I do this with an example, I'll be very grateful.
My question is, having this schema, how can I add a song to a playlist? there are two models affected by the query, song (where i am trying to add) and playlist.
My schema:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
model User {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
email String #unique
firstName String
lastName String
password String
playlists Playlist[]
}
// here
model Song {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
name String
artist Artist #relation(fields: [artistId], references: [id])
artistId Int
playlists Playlist[]
duration Int
url String
}
model Artist {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
songs Song[]
name String #unique
}
// here
model Playlist {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
name String
songs Song[]
user User #relation(fields: [userId], references: [id])
userId Int
}
I am trying to add the song like this:
let songId = 1;
let playlistId = 1;
let lists;
// get the playlists the song is part of
lists = await prisma.song.findFirst({
select: {
playlists: true
},
where: {
id: +songId
}
})
// get the playlist data i need
const list = await prisma.playlist.findUnique({
where: {
id: playlistId
}
})
// create the array for update with the data
// plus the data I want to add
lists = [ ...lists.playlists, list ]
// trying to update the old array with the new data (lists)
// this is what i'm doing wrong, help please
await prisma.song.update({
where: { id: +songId },
data:{
playlists: lists
}
})
after many tries i finally got what i want if anyone knows a better way please tell me, i want to learn, for now this is my solution:
i need to send each value as id: playlistId
const song = await prisma.song.findUnique({
select: {
playlists: true
},
where: {
id: +songId
}
})
// get an array of objects, id: playlistId
const songPlaylistsIds = song.playlists.map( playlist => ({id: playlist.id}))
// I prepare the array with the content that already exists plus the new content that I want to add:
const playlists = [...songPlaylistsIds, { id: playlistId}]
await prisma.song.update({
where: { id: +songId },
data:{
playlists: {
// finally for each object in the array i get, id: playlistId and it works.
set: playlists.map( playlistSong => ({ ...playlistSong }))
}
}
})
Problems I had doing this: I was wrong in thinking that it should work as simple as playlist: lists I wanted to change the content to a new one but I couldn't, I needed to send the values one by one.
Another error when I get the content of the playlists. I had the full object but just needed to send the id.
And lastly, in the prisma documentation, there is a method like set, push, but this method doesn't work, at least I don't know how to make push work
I am creating a basic crud api with nodejs and prisma. My Schema is as follows:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
model Category {
id String #unique #default(cuid())
title String
description String
products Product[]
}
model Product {
id String #unique #default(cuid())
title String
description String
price Float
createdAt DateTime #default(now())
updatedAt DateTime?
category Category? #relation(fields: [categoryId], references: [id])
categoryId String?
}
I am trying to make the products field in the Category model optional. But Prisma doesn't allow that. But I want my users to create a category even without creating a post or vice-versa. How can I get around this?
According to the Prisma documentation, lists cannot be optional: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#remarks-5
However, the products field not being optional does not mean that it cannot be an empty when a new Category is created:
await prisma.category.create({
data: {
title: 'books',
description: 'books',
products: {},
}
})
Then you can create or connect the product later:
await prisma.category.update({
where: {
id: "category-id"
},
data: {
products: {
connect: {
id: "product-id"
}
}
}
})
I need help working through this error. I have a one-to many-self-relationship that dictates all the comments/children a post will have. see schema below.
model Post {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
description String
postedBy User? #relation(fields: [postedById], references: [id])
postedById Int?
votes Vote[]
comments Post[] #relation(name: "comment")
parent Post? #relation(name: "comment", fields: [parentId], references: [id])
parentId Int? #default(0)
}
As you can see, the parentId defaults to zero. I always want the root post to have a parentId of zero to signify that it is a root post because there is no Post with Id 0 for the foreign key to reference.
When I added the one-many-self-relationship with default 0 my prisma populated correctly, ignoring foreign-key errors in my db as seen below.
The parent field is null? or undefined? because the parentId = 0 doesn't exist. That is good because those are root posts. The problem is when I try and make a post mutation using gql I get a foreign key error because there is no post with id 0. See mutation below.
const { userId, prisma } = context;
if (userId === null) throw new Error("Not Authenticated");
const newPost = await prisma.post.create({
data: {
description: args.description,
postedBy: { connect: { id: userId } },
parent: { connect: { id: args.parentId } },
},
});
context.pubsub.publish("NEW_POST", newPost);
return newPost;
}
if I replace args.parentId = 0 I will get the foreign key error in gql, if args.parentId = 1 I will not get an error but the post is no longer considered a root post (bad).
can someone please offer me some guidance with this issue? I would like to ignore the foreign key error if possible.
I have looked into the referential actions (seen below) to set a default on post but I don't think my use case is the purpose of referential actions so I am not sure where to look...
model Post {
id Int #id #default(autoincrement())
title String
authorUsername String? #default("anonymous")
author User? #relation(fields: [authorUsername], references: [username], onUpdate: SetDefault)
}