Prisma2: use user id in related table in create function - prisma

I am trying to create a user that is the child of another user. I am managing this relationship by having a User table where all users, parents and children, are stored. There is a seperate table that just has the id of the child and the id of the parent.
My problem is that when I create a child account I want to create an entry in the Relationship table using the user id that would be created. I am not sure at all how I should go about this.
// schema.sql
CREATE TABLE "public"."Relationship" (
id SERIAL PRIMARY KEY NOT NULL,
parent_id INT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES "User" (id),
child_id INT NOT NULL,
FOREIGN KEY (child_id) REFERENCES "User" (id)
)
CREATE TABLE "public"."User" (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(128) NOT NULL,
email VARCHAR(128) UNIQUE,
password VARCHAR(128) NOT NULL,
isChild BOOLEAN NOT NULL DEFAULT false
created_at TIMESTAMP NOT NULL DEFAULT NOW();
);
// CreateChild User mutation
export const createChildAccount = mutationField('createChildAccount', {
type: 'User',
args: {
name: stringArg({ required: true }),
password: stringArg({ required: true }),
},
resolve: async (_parent, { name, password }, ctx) => {
const userId = getUserId(ctx);
if (!userId) {
// TODO -think I might need to throw an error here
return;
}
const user = await ctx.prisma.user.create({
data: {
name,
password,
ischild: true,
child: {
create: { child_id: ???????? },
},
parent: {
connect: {id: userId}
}
},
});
return user;
},
});
Should I actually be creating a Relationship and then using that to connect the parent and create the child?

If you are just storing the id of the child and the parent, I would suggest using a self-relation to the same table hainv something like this in the schema
model User {
id Int #default(autoincrement()) #id
name String
parent User? #relation("UserToUser", fields: [parent_id], references: [id])
parent_id Int? #unique
createdAt DateTime #default(now())
}
For the same in SQL, it would be as follows
create table "User" (
createdAt timestamp default now(),
"id" serial primary key,
"name" varchar not null,
parent_id int unique,
foreign key (parent_id) references "User"("id") on delete set null on update cascade
)
Then your create/update call would be quite simple in the following manner
const parent = await prisma.user.create({
data: {
name: 'abc',
},
})
await prisma.user.create({
data: {
name: 'def',
parent: {
connect: {
id: parent.id,
},
},
},
})

In hindsight it was an easy solution. I created the entry in the User and then created an entry in the Relationship table where I connected the parent and child accounts
export const createChildAccount = mutationField('createChildAccount', {
type: 'User',
args: {
name: stringArg({ required: true }),
password: stringArg({ required: true }),
},
resolve: async (_parent, { name, password }, ctx) => {
const userId = getUserId(ctx);
if (!userId) {
return;
}
const user = await ctx.prisma.user.create({
data: {
name,
password,
ischild: true,
},
});
await ctx.prisma.relationship.create({
data: {
parent: {
connect: {
id: userId,
},
},
child: {
connect: {
id: user.id,
},
},
},
});
return user;
},
});

Related

How to connect to more than one relation using Prisma Client upsert function?

When I'm trying to connect more than one relation to my Profile table I get the following error message:
PrismaClientValidationError:
Invalid `prisma.connection.upsert()` invocation:
{
where: {
id: ''
},
update: {
userOneId: 'clbcb6z58000gyb31ez95frvh',
userTwoId: 'clbcaz4bl000ayb31icwpzphu',
isUserOneApproved: false,
isUserTwoApproved: true,
connectionRequestedOnDate: '2022-12-07T21:48:12.441Z',
connectionAcceptedOnDate: undefined
},
create: {
userOneId: 'clbcb6z58000gyb31ez95frvh',
~~~~~~~~~~~~~
userTwoId: 'clbcaz4bl000ayb31icwpzphu',
~~~~~~~~
isUserOneApproved: false,
isUserTwoApproved: true,
connectionRequestedOnDate: '2022-12-07T21:48:12.441Z',
connectionAcceptedOnDate: undefined,
userOne: {
connect: {
id: 'clbcb6z58000gyb31ez95frvh'
}
},
userTwo: {
connect: {
id: 'clbcaz4bl000ayb31icwpzphu'
}
},
profile: {
connect: {
id: 'clbcb71l2000kyb31cclk79z3'
}
}
}
}
Unknown arg `userOneId` in create.userOneId for type ConnectionCreateInput. Did you mean `userOne`? Available args:
type ConnectionCreateInput {
id?: String
isUserOneApproved: Boolean
isUserTwoApproved: Boolean
connectionRequestedOnDate?: DateTime | Null
connectionAcceptedOnDate?: DateTime | Null
userOne: ProfileCreateNestedOneWithoutUserOneInput
userTwo: ProfileCreateNestedOneWithoutUserTwoInput
profile: ProfileCreateNestedOneWithoutConnectionInput
}
Unknown arg `userTwoId` in create.userTwoId for type ConnectionCreateInput. Did you mean `userTwo`? Available args:
type ConnectionCreateInput {
id?: String
isUserOneApproved: Boolean
isUserTwoApproved: Boolean
connectionRequestedOnDate?: DateTime | Null
connectionAcceptedOnDate?: DateTime | Null
userOne: ProfileCreateNestedOneWithoutUserOneInput
userTwo: ProfileCreateNestedOneWithoutUserTwoInput
profile: ProfileCreateNestedOneWithoutConnectionInput
}
This is how my prisma upsert function is currently defined:
const createOrUpdateRole = await prisma.connection.upsert({
where: { id: id },
update: {
userOneId: userOneId,
userTwoId: userTwoId,
isUserOneApproved,
isUserTwoApproved,
connectionRequestedOnDate,
connectionAcceptedOnDate,
},
create: {
userOneId: userOneId,
userTwoId: userTwoId,
isUserOneApproved,
isUserTwoApproved,
connectionRequestedOnDate,
connectionAcceptedOnDate,
userOne: { connect: { id: userOneId } },
userTwo: { connect: { id: userTwoId } },
profile: { connect: { id: profileId } },
},
});
My expectation when running the function was to connect userOne and userTwo to my Profile table using their respective ids. The reason for that was that I could then query for the connection and then read the profile data for userOne and userTwo.
This is my models defined in schema.prisma:
model Profile {
id String #id #default(cuid())
firstName String?
lastName String?
image String? #db.Text
userId String
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
sensitive Sensitive[]
userOne Connection[] #relation("userOne")
userTwo Connection[] #relation("userTwo")
connection Connection[] #relation("profile")
##index([userId])
}
model Connection {
id String #id #default(cuid())
isUserOneApproved Boolean
isUserTwoApproved Boolean
connectionRequestedOnDate DateTime?
connectionAcceptedOnDate DateTime?
userOneId String
userOne Profile #relation("userOne", fields: [userOneId], references: [id], onDelete: Cascade)
userTwoId String
userTwo Profile #relation("userTwo", fields: [userTwoId], references: [id], onDelete: Cascade)
profileId String
profile Profile #relation("profile", fields: [profileId], references: [id], onDelete: Cascade)
##index([userOneId])
##index([userTwoId])
##index([profileId])
}
If I decide to one connect to only one of them, for instance userOneId using userOne: { connect: { id: userOneId } }, then the function runs as expected, but as soon as I start to define more than one I get the error mentioned above. What have I missed?
The error is pretty self-descriptive:
Unknown arg `userOneId` in create.userOneId for type ConnectionCreateInput
Since you are already passing userOne: {connect: {id: '.....' } } in your Prisma operation, you do not (and should not) also pass userOneId. You just need userOne: {connect..... and userTwo: {connect......
Your operation will end up looking like this:
const createOrUpdateRole = await prisma.connection.upsert({
where: {
id: id
},
update: {
// You can have __either__ `userOneId: 'value'` or `userOne: {connect....` here
// but not both
userOneId: userOneId,
userTwoId: userTwoId,
isUserOneApproved,
isUserTwoApproved,
connectionRequestedOnDate,
connectionAcceptedOnDate,
},
create: {
isUserOneApproved,
isUserTwoApproved,
connectionRequestedOnDate,
connectionAcceptedOnDate,
userOne: {
connect: {
id: userOneId
}
},
userTwo: {
connect: {
id: userTwoId
}
},
profile: {
connect: {
id: profileId
}
},
},
});

Error: there is no unique or exclusion constraint matching the ON CONFLICT specification (TypeORM)

Error: "there is no unique or exclusion constraint matching the ON CONFLICT specification"
I am trying to perform an upsert operation but keep running into this error. I've tried multiple related answers but still no solution.
This is the migration file to create the table
import { MigrationInterface, QueryRunner } from 'typeorm';
export class createTableNotifications1666922029458 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "notifications" (
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
"user_id" UUID REFERENCES "users",
"summary" VARCHAR(255),
"description" VARCHAR(255),
"external_id" VARCHAR(255) UNIQUE,
"is_dismissed" BOOLEAN DEFAULT 'false',
"dismiss_reason" VARCHAR(255),
"event_begins" TIMESTAMP WITH TIME ZONE,
"event_ends" TIMESTAMP WITH TIME ZONE,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX ON "notifications" ("user_id");
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP TABLE IF EXISTS "notifications";
`);
}
}
This is the entity for the table
#Entity('notifications')
#Unique(['external_id'])
export class Notification extends BaseEntity {
constructor({ id, ...event }: Partial<Notification> = {}, options = { generateId: false }) {
super(id, options);
Object.assign(this, { ...event });
}
#Column({
type: 'uuid',
nullable: false,
})
user_id?: string;
#Column({
type: 'varchar',
length: 255,
})
summary?: string;
#Column({
type: 'varchar',
length: 255,
})
description?: string;
#Column({
type: 'varchar',
length: 255,
unique: true,
})
#Index({ unique: true })
external_id?: string;
#Column({
type: 'timestamptz',
})
event_begins?: Date;
#Column({
type: 'timestamptz',
})
event_ends?: Date;
#Column({
type: 'boolean',
})
is_dismissed?: boolean;
#Column({
type: 'varchar',
length: 255,
})
dismiss_reason?: string;
#ManyToOne(() => User, (user) => user.notifications)
#JoinColumn({ name: 'user_id' })
user?: User;
}
This is the custom upsert function(using the default TypeORM upsert function produces the same error)
async upsert(item: T, conflictTarget: string[]): Promise<T> {
const keys = Object.keys(item);
const keysForUpdate = keys.filter((e) => ![...conflictTarget, 'id'].includes(e));
return this.orm
.createQueryBuilder()
.insert()
.into(this.Entity)
.values([item])
.orUpdate({ conflict_target: conflictTarget, overwrite: keysForUpdate })
.returning('*')
.execute()
.then(({ raw: [{ id }] }: UpdateResult) => this.orm.findOne(id));
}
And this is the function where upsert is called with the conflict targets specified
async saveCalendarEvent(event: UpdateCalendarEventDto, user_id: string): Promise<UpdateCalendarEventDto> {
const user = await this.userRepository.orm.findOne(user_id);
if (!user) throw new NotFoundException(`User with ID: ${user_id} does not exist!`);
const { id, summary, description, event_begins, event_ends, external_id, is_dismissed, dismiss_reason } = event;
console.log(event);
const newNotification = new Notification({
id,
user_id,
summary,
description,
external_id,
event_begins,
event_ends,
is_dismissed,
dismiss_reason,
});
await this.notificationRepository.upsert(newNotification, ['id', 'external_id']);
return event;
}
My best guess is that I need to add some constraints in the migration file, but can't figure out what exactly is needed as I'm very new to backend development. external_id can be null, but in the case that the incoming data has an external_id property it should be unique
Any help will be appreciated!

Get created relation on upsert in Prisma

I'm just getting started with Prisma, and have the following schema:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Client {
id Int #id #default(autoincrement())
email String #unique
first String?
last String?
tickets Ticket[]
}
model Ticket {
id String #id #default(uuid())
purchasedAt DateTime #default(now())
pricePaid Int
currency String #db.VarChar(3)
productId String
client Client #relation(fields: [clientId], references: [id])
clientId Int
}
When a client buys a new ticket, I want to create a new ticket entry, and the associated client entry, if the client doesn't exist. Much to my surprise, the following Just Worked:
const ticketOrder = {
// details
};
const client = await prisma.client.upsert({
where: {
email: email,
}, update: {
tickets: {
create: [ ticketOrder ]
}
}, create: {
first: first,
last: last,
email: email,
tickets: {
create: [ ticketOrder ]
}
}
});
However, what gets returned is just the client entry, and what I need is the newly created ticket entry (actually, just the id of the newly created ticket entry). Is there any way to get that in one go, or do I have to do some sort of query after the upsert executes?
You can use select or include as you would in a find operation to include referenced objects, e.g.
const client = await prisma.client.upsert({
where: {
email: email,
},
update: {
tickets: {
create: [ ticketOrder ]
}
},
create: {
first: first,
last: last,
email: email,
tickets: {
create: [ ticketOrder ]
}
},
include: {
tickets: true
}
});
This would return all tickets.
To me, it seems strange to upsert the client, if you primarily want to create at ticket. You could instead create a ticket and create or connect the client:
const ticket = await prisma.ticket.create({
data: {
// ...
client: {
connectOrCreate: // ...
}
},
})
See: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#connect-or-create-a-record

How can I create a nested item in the DB?

I am having several problems when I try to create a new user with a nested related table.
My schema is like this:
model User {
id Int #id #default(autoincrement())
username String #unique
email String #unique
avatar String #default("none")
role Role #default(USER)
password String
maxScore Int #default(0)
UserCategoryStats UserCategoryStats[]
}
model UserCategoryStats {
id Int #id #default(autoincrement())
user User #relation(fields: [userId], references: [id])
userId Int
category Category #relation(fields: [categoryId], references: [id])
categoryId Int
correctAnswers Int #default(0)
incorrectAnswers Int #default(0)
totalAnswers Int #default(0)
timesChosen Int #default(0)
}
And I am just trying to create an user with one(or more) UserCategoryStats like the docs say
Like this:
const newUser = await prisma.user.create({
data: {
username,
email,
password: hashedPassword,
UserCategoryStats: {
create: { //Here is the error
correctAnswers: 0,
incorrectAnswers: 0,
totalAnswers: 0,
timesChosen: 0,
},
},
},
include: {
UserCategoryStats: true,
},
});
This is giving me this Typescript error :
Type '{ correctAnswers: number; incorrectAnswers: number; totalAnswers: number; timesChosen: number; }' is not assignable to type '(Without<UserCategoryStatsCreateWithoutUserInput, UserCategoryStatsUncheckedCreateWithoutUserInput> & UserCategoryStatsUncheckedCreateWithoutUserInput) | ... 5 more ... | undefined'.
Type '{ correctAnswers: number; incorrectAnswers: number; totalAnswers: number; timesChosen: number; }' is not assignable to type 'undefined'
I have tried with multiple values but I simply dont know what is wrong. I am not able to create nested properties.
What am I doing wrong?
Considering the Prisma Query and Schema that you have shared there were no typescript errors, it can be that your PrismaClient is not in sync with the schema, you can execute npx prisma generate to verify if both are in sync.
Here's a working example:
main.ts
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient({
log: ['query'],
});
async function main() {
const newUser = await prisma.user.create({
data: {
username: 'alice',
email: 'alice#prisma.io',
password: 'Prisma#111',
UserCategoryStats: {
create: {
//Here is the error
correctAnswers: 0,
incorrectAnswers: 0,
totalAnswers: 0,
timesChosen: 0,
},
},
},
include: {
UserCategoryStats: true,
},
});
console.log(newUser);
}
main()
.catch((e) => {
throw e;
})
.finally(async () => {
await prisma.$disconnect();
});
Response:
> ts-node index.ts
prisma:query BEGIN
prisma:query INSERT INTO "public"."User" ("username","email","avatar","password","maxScore") VALUES ($1,$2,$3,$4,$5) RETURNING "public"."User"."id"
prisma:query INSERT INTO "public"."UserCategoryStats" ("userId","correctAnswers","incorrectAnswers","totalAnswers","timesChosen") VALUES ($1,$2,$3,$4,$5) RETURNING "public"."UserCategoryStats"."id"
prisma:query SELECT "public"."User"."id", "public"."User"."username", "public"."User"."email", "public"."User"."avatar", "public"."User"."password", "public"."User"."maxScore" FROM "public"."User" WHERE "public"."User"."id" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."UserCategoryStats"."id", "public"."UserCategoryStats"."userId", "public"."UserCategoryStats"."correctAnswers", "public"."UserCategoryStats"."incorrectAnswers", "public"."UserCategoryStats"."totalAnswers", "public"."UserCategoryStats"."timesChosen" FROM "public"."UserCategoryStats" WHERE "public"."UserCategoryStats"."userId" IN ($1) OFFSET $2
prisma:query COMMIT
{
id: 1,
username: 'alice',
email: 'alice#prisma.io',
avatar: 'none',
password: 'Prisma#111',
maxScore: 0,
UserCategoryStats: [
{
id: 1,
userId: 1,
correctAnswers: 0,
incorrectAnswers: 0,
totalAnswers: 0,
timesChosen: 0
}
]
}
Inserted Data:

How to connect a many to many relationship using Prisma

I am trying to create and connect a record in a Prisma many to many relationship, but the connection part is not working for me.
Here are my Prisma models:
model Ingredient {
id Int #id #default(autoincrement())
name String
createdAt DateTime #default(now())
calories Int
protein Int
fat Int
carbs Int
netCarbs Int
metricQuantity Int
metricUnit String
imperialQuantity Int
imperialUnit String
recipes IngredientsOnRecipes[]
categories CategoriesOnIngredients[]
}
model IngredientCategory {
id Int #id #default(autoincrement())
name String
ingredients CategoriesOnIngredients[]
}
model CategoriesOnIngredients {
ingredient Ingredient #relation(fields: [ingredientId], references: [id])
ingredientId Int // relation scalar field (used in the `#relation` attribute above)
ingredientCategory IngredientCategory #relation(fields: [ingredientCategoryId], references: [id])
ingredientCategoryId Int // relation scalar field (used in the `#relation` attribute above)
assignedAt DateTime #default(now())
##id([ingredientId, ingredientCategoryId])
}
Here is the primsa query I am running:
const ingredient = await prisma.ingredient.create({
data: {
name: title,
metricQuantity: parseInt(quantityMetric),
metricUnit: unitMetric,
imperialQuantity: parseInt(quantityImperial),
imperialUnit: unitImperial,
calories: parseInt(calories),
netCarbs: parseInt(netCarbs),
carbs: parseInt(carbs),
protein: parseInt(protein),
fat: parseInt(fat),
categories: {
ingredientcategory: {
connect: { id: parseInt(categoryId) },
},
},
},
});
Creating a new ingredient works perfectly, but when I add this section:
categories: {
ingredientcategory: {
connect: { id: parseInt(categoryId) },
},
},
I get the following error:
Unknown arg ingredientcategory in data.categories.ingredientcategory for type CategoriesOnIngredientsCreateNestedManyWithoutIngredientInput. Did you mean createMany? Available args:
type CategoriesOnIngredientsCreateNestedManyWithoutIngredientInput {
create?: CategoriesOnIngredientsCreateWithoutIngredientInput | List | CategoriesOnIngredientsUncheckedCreateWithoutIngredientInput | List
connectOrCreate?: CategoriesOnIngredientsCreateOrConnectWithoutIngredientInput | List
createMany?: CategoriesOnIngredientsCreateManyIngredientInputEnvelope
connect?: CategoriesOnIngredientsWhereUniqueInput | List
}
You can try executing the following:
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
const saveData = async () => {
const ingredient = await prisma.ingredient.create({
data: {
name: 'ingredient1',
categories: {
create: {
ingredientCategory: {
create: {
name: 'category1',
},
}
}
},
},
select: {
id: true,
name: true,
categories: {
select: {
ingredientId: true,
ingredientCategory: true,
}
},
},
});
console.log(JSON.stringify(ingredient, null, 2));
}
saveData()
And you will have the following:
I managed to get it to work, I had missed create:{} from my Prisma query.
const ingredient = await prisma.ingredient.create({
data: {
name: title,
metricQuantity: parseInt(quantityMetric),
metricUnit: unitMetric,
imperialQuantity: parseInt(quantityImperial),
imperialUnit: unitImperial,
calories: parseInt(calories),
netCarbs: parseInt(netCarbs),
carbs: parseInt(carbs),
protein: parseInt(protein),
fat: parseInt(fat),
categories: {
create: {
ingredientCategory: {
connect: { id: parseInt(categoryId) },
},
},
},
},
});