#postgresql #next.js #prisma #next-auth
#postgresql #next.js #prisma #далее -авторизация
Вопрос:
Я создаю следующее JS-приложение с prisma и postgres.
У меня есть 2 таблицы: User и Profile
Их структура схемы prisma выглядит следующим образом:
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
// foreign keys
sessions Session[]
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
isAdmin Boolean @default(false)
firstName String
lastName String
email String @unique
phone String
address String
gender String
image Bytes
guardianName1 String
guardianPhone1 String
guardianRelation1 String
guardianName2 String?
guardianPhone2 String?
guardianRelation2 String?
guardianName3 String?
guardianPhone3 String?
guardianRelation3 String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// foreign keys
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @default(cuid()) // relation scalar field (used in the `@relation` attribute above)
requests Request[]
}
Я также использую next-auth
для аутентификации часть этого приложения. Поэтому, когда пользователь регистрируется после проверки его электронной почты, next-auth сам добавляет запись пользователя в таблицу пользователей.
Пока здесь нет проблем.
Затем, когда пользователь впервые открывает свою панель мониторинга, ему отображается форма для заполнения, после отправки этой формы запись должна быть вставлена в таблицу профилей. Поскольку таблицы Profile и User связаны, их также необходимо подключить.
Поэтому, когда пользователь отправляет форму сведений о профиле, я делаю это:
try {
const newProfileData = {
// other fields data here...
user: {
connect: { id: '1' } // where User table already has a record with - 'id': 1
}
};
const profile = await prisma.profile.create({ data: newProfileData, include: { user: true } });
if(profile) {
console.log("Created: ", profile);
res.status(200).json({ msg: 'Successfully Created Profile!' });
}
}
catch(err)
{
console.log(err);
}
Но при запуске этого кода я получаю сообщение об ошибке:
The change you are trying to make would violate the required relation 'ProfileToUser' between the `Profile` and `User` models.
...
code: 'P2014',
clientVersion: '2.30.3',
meta: {
relation_name: 'ProfileToUser',
model_a_name: 'Profile',
model_b_name: 'User'
}
Как это можно решить?
Я даже попробовал другой способ (т. Е. Обновить существующего пользователя и Создать связанную с ним запись профиля):
const user = await prisma.user.update({
where: {
email: req.body.email,
},
data: {
profile: {
create: {
// data fields here... (without the user field)
},
},
},
});
Но это также дает ту же ошибку …
Я хочу понять, почему возникает ошибка. Разве это не правильный способ создать запись для отношения 1 к 1 с помощью prisma-client?
Комментарии:
1. Соотношение 1: 1 всегда вызывает сомнения, в данном случае оно совершенно не нужно. Единственный атрибут в
profile
этом заключается не вuser
том, чтобы бытьisAdmin
столбцом. Если, конечно, вы не разрешите, чтобы столбцыname
и2. Нет, на самом деле в таблице профилей намного больше столбцов, чем указано (вместо того, чтобы писать все это, я написал —
// other attributes here
)3. Вы пытались поменять свой
user: { connect: {id: '1'
на простойuserId: '1'
и сделатьinclude: { user: false }
? Мне интересноuserId String @default(cuid())
, заставляет ли ваше определение генерировать новый случайный идентификатор, который не соответствует ни одному существующемуUser
, когда вы выпускаетеprisma.profile.create()
. И когда вы указываетеinclude: { user: true }
во время этого создания, не пытается ли он создать еще одного нового пользователя с еще одним автоматически созданнымid
?
Ответ №1:
Исправление:
Я думаю, вам нужно удалить @default(cuid())
из определения поля профиля userId
.
model Profile {
//...
// foreign keys
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String // relation scalar field (used in the `@relation` attribute above)
//...
}
А также избавьтесь от include: { user: true }
:
const profile = await prisma.profile.create({ data: newProfileData});
Объяснение:
Профили user
и userId
поля напрямую не преобразуются в фактические столбцы в БД, а являются полями, которые позволяют Prisma обрабатывать связь между отношениями. В конечном итоге он преобразуется в PostgreSQL
create table profile(
--...
userId text references user (id),
--...
);
И позже Prisma заполнит это поле именем вашего пользователя id
при выдаче user:{connect:{id:'1'}}
. Что могло произойти, так это то, что при использовании @default(cuid())
userId
определения поля in вы вмешались в этот процесс. Теперь столбец заканчивается как
userId text default gen_random_uuid() references user (id)
и всякий раз, когда вы создаете профиль, вводится новая строка без указания вашей собственной userId
(что Prisma, вероятно, пытается сделать, прежде чем попытается связать вашего пользователя), генерируется случайный идентификатор, который не соответствует ни одному существующему пользователю, что нарушает ограничение ссылки.
Это то, что и / или ваше использование include: { user: true }
что-то портит, порождая отдельного нового пользователя, даже если вы пытались связать свой профиль с существующим. Но я бы ожидал, что это будет просто нежелательным побочным эффектом, из-за которого ваш код порождает бесполезный пользовательский объект и строку каждый раз, когда вы создаете профиль.
Как только вы избавитесь от @default(cuid())
, вы также можете просто создать отдельный, несвязанный профиль, а затем связать его с соответствующим пользователем позже с помощью инструкции update.
Комментарии:
1. Большое вам спасибо! Я сделал это, как вы сказали, и запись профиля успешно создается.
Ответ №2:
Объедините две таблицы в одну, что-то вроде:
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
isAdmin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// foreign keys
sessions Session[]
}
Если вам абсолютно необходимо иметь отношение к профилю, создайте представление базы данных:
create view Profile as
select
id,
isAdmin,
name,
email,
createdAt,
updatedAt,
userId
from user
и сопоставьте ее как отношение только для чтения, но я не вижу смысла.
Комментарии:
1. Я использую next-auth для части аутентификации этого проекта. Поэтому, когда пользователь регистрируется, next-auth вставляет свою запись в таблицу User. Затем, когда пользователь впервые открывает свою панель мониторинга, требуются другие сведения о пользователе, которые будут вставлены в таблицу профилей. Разве это не решение предоставляемой ошибки, например, как вставить запись в таблицу, которая имеет отношение 1 к 1 с другой? Как без изменения структуры БД? Кроме того, я хочу понять причину этой ошибки