В предыдущей статье мы разворачивали Graphcool Prisma с нуля. На выходе кроме всего прочего мы получили веб-интерфейс, через который можно было добавлять новые топики и публиковать их.
Сегодня мы добавим пользователей, а так же связь Пользователь-Топики и проверку на владельца записей, чтобы публиковать черновики могли только их владельцы.
Добавим модель пользователя.
Прежде чем связать добавить связь топик-пользователь, просто добавим самостоятельную модель User (Пользователь). Для этого откроем файл server/database/datamodel.graphql
Сейчас там прописана только модель Post (Топик).
type Post {id:ID! @unique
isPublished: Boolean!title: String!text: String!}
Допишем ниже:
type User {
id:ID! @unique
email:String! @unique
password:String!
name:String!}
Схема описана, теперь надо ее задеплоить, чтобы призма обновила структуру базы данных и сгенерировала необходимые методы для API и работы с базой данных. Для этого перейдем в папку server/ и выполним prisma deploy
Результат выполнения:
prisma deploy
Deploying service `hello-world` to stage `dev` on cluster `local` 52ms
Changes:User(Type)+ Created type `User`+ Created field `id`of type `GraphQLID!`+ Created field `email`of type `String!`+ Created field `password`of type `String!`+ Created field `name`of type `String!`+ Created field `updatedAt`of type `DateTime!`+ Created field `createdAt`of type `DateTime!`
Applying changes 1.1s
Hooks:
Writing database schema to `src/generated/prisma.graphql` 97ms
query - это обычный запрос, то есть на получение данных. mutation - это запрос на изменение данных. На самом деле это все очень условно, так как GraphQL особо ничего не знает о выполняемых эти запросы/мутации резолверах (функции, обрабатывающие запросы), резолверы на query могут выполнять запросы на обновления, а мутации просто выборки данных. Но есть одна важная деталь: query за один раз можно выполнить сразу несколько и параллельно, в то время как mutation выполняется только поштучно и последовательно.
В данном случае у нас получается, что модель есть, но мы не можем с ней ничего делать, то есть через API мы не можем слать запросы ни на создание пользователей, ни на получение их списков, ничего.
Откроем файл server/src/schema.graphql и допишем в нем в Query users: [User!]! и в Mutation createUser(name: String!, email: String!, password: String!): User
итого получится
# import Post from"./generated/prisma.graphql"
type Query {feed:[Post!]!drafts:[Post!]!post(id:ID!): Post
users:[User!]}
type Mutation {createDraft(title: String!,text: String): Post
deletePost(id:ID!): Post
publish(id:ID!): Post
createUser(name: String!,email: String!,password: String!): User
}
Обратите внимание на # import Post from "./generated/prisma.graphql"
Это не комментарий, это так прописана подгрузка типов нашего приложения. Редактировть тот файл нельзя, он генерируется призмой при деплое.
Перезапустим сервер приложения, чтобы вступила в силу новая схема (нажмем Ctrl+C и опять выполним yarn start) и обновим страницу.
Это происходит потому что хотя у нас схема описана, не прописан резолвер на обработку этого запроса. То есть граф обрабатывает запрос, схема вся валидная, выполнение разрешено, но данных нет и не прописана функция на обработку запроса (возврат данных). В таком случае он просто возвращает пустое значение. А вот если бы мы прописали в Query users: [User!]! вместо users: [User!], то тут бы мы получили ошибку от графа, так как знак ! сигнализирует о запрете нулевого значения, то есть обязан быть ненулевой список, и как следует из указания User!, список этот не должен содержать нулевые значения пользователей.
Здесь на всякий случай еще раз объясню структуру запроса
mutation {createUser(name:"Test"email:"test@local.host"password:"123123"){
id
name
email
password
}}
mutation, логично, указывает на то, что это именно запрос из мутаций выполняется, а не просто query. Это в графе разные группы запросов.
createUser - это название конкретной операции, мы так назвали ее в схеме выше.
Все что в круглых скобках - это передаваемые параметры в запрос.
В фигурных - структура возвращаемых данных. То есть в результате выполнения, если будет создан пользователь, мы сразу получим в ответ указанные поля из данных этого пользователя.
Итак, допишем мутацию на создание пользователя. Для этого открываем файл server/src/index.js и в Mutation дописываем наш обработчик createUser.
bcrypt я подключил в этом файле выше через const bcrypt = require('bcryptjs')
Так как в этой версии приложения пакет bcryptjs не был установлен, устанавливаем его через команду yarn add bcryptjs и после этого опять запускаем сервер.
Вот теперь пользователь был создан и на выходе мы получили пароль не в чистом виде, как его передавали, а сразу его хеш.
Обратите внимание, что мы не писали никаких запросов на непосредственную работу с базой данных. За нас все необходимые запросы создала призма при деплое новой схемы (когда выполняли prisma deploy). Напомню, что запросы эти пишутся в файл server/src/generated/prisma.graphql
А если еще более правильно выражаться, то там не запросы, а API-схемы для еще более низкого слоя всей этой системы - API-сервера призмы, что крутится на порту 4466. То есть получается, что наш проект крутится в своей папке, для него есть своя схема, через которую запросы транслируются на сервер призмы, которая в свою очередь работает с базой данных. При этом в призму мы деплоим изменения только в типах объектов схемы, запросы мы туда не деплоим, это уже наш локальный вопрос.
Авторизация пользователей.
Ну а теперь добавим непосредственно авторизацию пользователей. Зачем нам пользователи без этого?
В схему в Mutation допишем login(email: String!, password: String!): User
и там же ниже допишем еще одну модель.
type AuthPayload {token: String!user: User!}
Это чтобы в ответ мы получали не только объект пользователя, но и токен.
Обратите внимание на приставку Bearer, ее необходимо указывать.
Вот теперь у нас есть не только создание пользователей, но и авторизация.
Связываем пользователей и топики.
Ну и последний штрих: настроим связи Топик-Пользователь и научимся получать топики конкретных пользователей и авторов топиков. Для этого нам надо подправить наши схемы пользователя и топика.
Допишем в модель Post author: User @relation(name: "UserPosts"), а в User posts: [Post!]! @relation(name: "UserPosts"). Получится
type Post {id:ID! @unique
isPublished: Boolean!title: String!text: String!author: User @relation(name:"UserPosts")}
type User {id:ID! @unique
email: String! @unique
password: String!name: String!posts:[Post!]! @relation(name:"UserPosts")}
Задеплоим нашу новую схему prisma deploy.
prisma deploy
Deploying service `hello-world` to stage `dev` on cluster `local` 128ms
Changes:Post(Type)+ Created field `author`of type `Relation`User(Type)+ Created field `posts`of type `[Relation!]!`UserPosts(Relation)+ Created relation between Post and User
Applying changes 1.0s
Hooks:
Writing database schema to `src/generated/prisma.graphql` 119ms
Что примечательно, призма не просто создала новую таблицу для хранения записей Топик-Пользователь, но даже настроила первичные-вторичные ключи.
Здесь мы просто дописали получение ID текущего пользователя и если был получен, то передаем в запрос создания топика объект с ID этого пользователя. При чем я специально оставил возможность передачи пустого объекта пользователя, чтобы оставить возможность публикации топиков и анонимно.
Вот теперь при создании топика, если пользователь авторизован, прописывается автор в топик.
Я уж не буду говорить, что сейчас доступны методы и на редактирование/удаление этих сущностей.
Вот так вот за вечер мы настроили себе платформу для регистрации/авторизации и публикации топиков, почти с нуля. На мой взгляд - очень неплохой результат.
Я не буду сейчас расписывать программирование фронта под все это (чтобы веб-морда для авторизации была и т.п.), это будет в следующем уроке. Скажу только что материал освоен и там не менее интересно, чем этот урок.