Как построить систему голосования за фильмы в реальном времени с помощью React.js 🤯


Что мы будем создавать?

Сначала мы попытаемся создать простое (на самом деле не такое уж и простое) приложение для просмотра фильмов. Где пользователи смогут голосовать за фильмы, писать комментарии и, возможно, добавлять фильмы.

Затем во второй части мы сможем углубиться, добавив систему бронирования фильмов и несколько кинотеатров. Для этого мы будем использовать Rocketgraph, так как он предоставляет полный бэкенд с auth ad db, поэтому нам не нужно беспокоиться об этом.

Для этого вам нужно будет определить пользователей, фильмы и место для их хранения:

  1. Аутентификация: Вам нужно хранить пользователей в таблице, скажем, в Postgres DB.

  2. Реальное время: Вам нужно получать комментарии и лайки в реальном времени из БД прямо на ваш фронтенд для пользователя.

  3. База данных: Где вы можете сопоставить пользователей с фильмами, а пользователей и фильмы с лайками.

💡Чтобы прочитать эту статью, вам нужно иметь базовое понимание React.js

Rocketgraph : Полноценный бэкенд, который превосходит Firebase и имеет открытый исходный код

Немного предыстории. Rocketgraph предоставляет полный бэкенд. Он поставляется с БД Postgres, консолью Hasura для управления Postgres и добавления слоя GraphQL к вашим данным, аутентификацией и бессерверными функциями.

Таким образом, мы предоставляем аутентификацию для ваших веб-приложений, GraphQL для работы в реальном времени, например, для сообщений, уведомлений, комментариев и т.д., и бессерверные функции для всего, что вы хотите разгрузить. Наше приложение Serverless Github автоматически компилирует ваш код Github в функции AWS Lambda.

Так что же такое GraphQL?

GraphQL — это язык, специфицированный Meta для выполнения запросов к вашим данным в режиме реального времени, запрашивая именно то, что вам нужно. Это отличается от традиционного подхода к API, где запрос кодируется в бэкенде, а фронтенд имеет очень мало контроля над тем, что/как запрашивать данные.

Подумайте об этом как о запросе JSON. Вы запрашиваете нужные вам данные в json-подобном запросе, и он возвращает именно эти поля.

В этой статье мы используем возможности GraphQL, React Apollo и Hasura для создания системы оценки и комментирования фильмов в режиме реального времени. Мы можем использовать эту же систему для бронирования билетов в кино.

TLDR-версия

Если вы просто хотите посмотреть код. Здесь находится база кода для этой статьи. Здесь вы можете увидеть больше примеров. Это программное обеспечение с открытым исходным кодом, лежащее в основе Rocketgraph.

Продолжайте читать

Круто, давайте начнем с основ.

Создайте проект react и разработайте front-end. Забудьте о бэкенде, мы добавим его позже.

mkdir movie-voting
cd movie-voting
Войдите в полноэкранный режим Выход из полноэкранного режима

Далее создайте базовое приложение react.

npx create-react-app ./
Войти в полноэкранный режим Выход из полноэкранного режима

Установите react router, чтобы иметь возможность перемещаться между страницами. Установите react-apollo и graphql для работы в реальном времени, как упоминалось выше.

yarn add react-router-dom
yarn add @apollo/client graphql
Войти в полноэкранный режим Выйдите из полноэкранного режима

Удалите из проекта лишние файлы, такие как логотип, и обновите файл index.js следующим образом:

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
      <Router>
          <Routes>
            <Route path="/login" />
            <Route path="/signup" />
            <Route path="/" element={<App />} />
          </Routes>
      </Router>
  </React.StrictMode>,
  document.getElementById("root")
);
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь нам нужно добавить App.js и компоненты login/signup. Это просто.

App.js

import logo from './logo.svg';
import './App.css';


const movies = [
  {
    name: 'Snatch',
    img: 'https://occ-0-3934-3211.1.nflxso.net/dnm/api/v6/E8vDc_W8CLv7-yMQu8KMEC7Rrr8/AAAABVJgO06RKuruJpcyezdM43Ai2ZjvNDmtbnwUXVtvXVhhvpL0tvhr4s9e3j8UojFCLao5a7v8Dg5kti1vFKcA0ldZXWnnC03nBRIt.jpg?r=cbf',
    likes: 10,
    state: true,
  }
];

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        movies.map(movie => {
          return (
              <div className="movie-box">
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.img} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><i class="fa fa-heart" style={{"color": "red"}}aria-hidden="true"></i></div>
                </div>
              </div>
          )
        })
      }
    </div>
  );
}

export default App;

Вход в полноэкранный режим Выход из полноэкранного режима

Теперь у нас есть базовый дизайн главной страницы. Давайте создадим страницы входа и регистрации.

signup.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';

export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();
    navigate("/");
  }

  return (
    <div>
      <h1>Signup</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Signup</button>
      </form>
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте посмотрим, сработало ли это

yarn start
Вход в полноэкранный режим Выход из полноэкранного режима

Поздравляем🥂, вы только что создали скелет для веб-приложения. Теперь нам осталось заполнить данные, аутентификацию и реальное время.

На помощь приходит Rocketgraph. Как создать бэкенд с аутентификацией и бессерверными функциями.

Просто зарегистрируйтесь и нажмите на кнопку «Создать проект» в панели инструментов:

Далее мы узнаем о некоторых удивительных функциях, которые волшебным образом создадут для вас бэкенд с помощью возможностей GraphQL.

Как только ваш проект будет создан, вы получите консоль Hasura и БД Postgres, как показано ниже. Пожалуйста, подождите, пока сервисы загрузятся. Это может занять около 3-5 минут.

Что такое Hasura?

Hasura — это удивительный инструмент с открытым исходным кодом, который GraphQLизирует вашу базу данных postgres. Это означает, что ваши данные все еще находятся в базе данных postgres, но вы получаете возможности GraphQL. В нем также есть редактор, который автоматически генерирует запросы GraphQL на основе ваших таблиц Postgres.

Возвращаясь к Rocketgraph

Когда ваш проект загрузится, вы получите ссылку на Hasura:

Откройте Hasura, и теперь мы начнем создавать таблицы для нашей базы данных.

Нам нужна таблица Movies, как показано ниже:

Нам также нужно дать доступ к ней пользователям. В Rocketgraph user — это роль, которая аутентифицируется, а наши JS SDK отправляют JWT с вашими запросами, так что вам не придется этого делать.

Перейдите на вкладку разрешений в фильмах и добавьте следующие разрешения:

Для insert установите следующие разрешения:

И для select — то же самое:

Введите GraphQL с помощью пакетов react-apollo и graphql. Apollo упрощает запрос к GraphQL непосредственно из React и предоставляет некоторые мощные функциональные возможности, такие как useSubscription, которые мы обсудим позже.

Давайте установим их.

yarn add @apollo/client graphql
Вход в полноэкранный режим Выход из полноэкранного режима

Нам также понадобятся некоторые пользовательские JS-библиотеки, чтобы аутентификация работала.

yarn add @rocketgraphql/react-apollo @rocketgraphql/rocketgraph-js-sdk
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы добавим аутентификацию в наш код, используя RApolloProvider, предоставленный @rocketgraphql/react-apollo.

Сначала создайте папку utils, а затем создайте в ней config.js со следующим содержимым:

import { createClient } from "@rocketgraphql/rocketgraph-js-sdk";
import Cookies from 'js-cookie';

const config = {
  baseURL: "https://backend-REPLACE",
};

const { auth } = createClient(config);

export { auth };

Войти в полноэкранный режим Выйти из полноэкранного режима

Замените вышеуказанный https://backend-REPLACE на url бэкенда в вашей приборной панели Rocketgraph:

Вы найдете его в разделе Auth.

Измените код в index.js на:

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import App from "./App";
import Signup from "./components/login";
import { RApolloProvider } from "@rocketgraphql/react-apollo";
import { auth } from "./utils/config";

ReactDOM.render(
  <React.StrictMode>
      <RApolloProvider auth={auth} gqlEndpoint="https://gqlEndpoint/v1/graphql">
        <Router>
            <Routes>
              <Route path="/login" element={<Signup />}/>
              <Route path="/signup" />
              <Route path="/" element={<App />} />
            </Routes>
        </Router>
      </RApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

Вход в полноэкранный режим Выход из полноэкранного режима

Измените вышеуказанный https://gqlEndpoint/v1/graphql на конечную точку graphql, которая находится здесь, в консоли Hasura:

Далее мы добавим auth.

login.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';
import { auth } from "../utils/config";

export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();

    // login
    try {
      await auth.signIn({email, password, provider: "local"});
    } catch (error) {
      alert("error logging in");
      console.error(error);
      return;
    }

    navigate("/");
  }

  return (
    <div>
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Login</button>
      </form>
    </div>
  );
}
Войдите в полноэкранный режим Выход из полноэкранного режима

signup.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';
import { auth } from "../utils/config";


export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();

    // login
    try {
      await auth.register({email, password});
    } catch (error) {
      alert("error logging in");
      console.error(error);
      return;
    }

    navigate("/");
  }

  return (
    <div>
      <h1>Signup</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Login</button>
      </form>
    </div>
  );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот и все. Rocketgraph сделает все остальное. Пользователи будут занесены в базу данных пользователей.

Вы можете проверить это, зарегистрировавшись и проверив, что пользователь создан.

Давайте создадим больше функций

Введите react-apollo.

App.js

import './App.css';
import { gql, useSubscription } from "@apollo/client";

const GET_MOVIES = gql`
  subscription {
    movies {
      id
      created_at
      name
      image
    }
  }
`;

function App() {
  const { data, loading } = useSubscription(GET_MOVIES);
  if (loading) {
    return <div>Loading</div>;
  }
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        data && data.movies && data.movies.length ?
        data.movies.map((movie, index) => {
          return (
              <div className="movie-box" key={index}>
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.image} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><i className="fa fa-heart" style={{"color": "red"}} aria-hidden="true"></i></div>
                </div>
              </div>
          )
        }) : "No movies"
      }
    </div>
  );
}

export default App;
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Вот и все, просто добавьте записи в вашу базу данных, и вы сможете увидеть их здесь в реальном времени.

Потрясающе 😎 Теперь, наконец, давайте добавим кнопку «Мне нравится».

Важная часть (идентификатор пользователя)

Сначала создайте таблицу likes с id, movie_id и user_id, как показано ниже в Hasura.

Нам нужно будет извлечь этот User-Id из самого jwt-токена.
Для этого

Шаг 1

Создайте новую роль с именем user и нажмите на Insert, чтобы отредактировать ее права.

Шаг 2

Разрешите роли пользователя изменять все. Установите эти флажки

Шаг 3 — самый важный

Установите идентификатор пользователя автоматически

Нажмите на предварительные настройки столбцов и выберите user-id. Установите значение X-Hasura-user-id.

И нажмите сохранить. Теперь мы подготовили нашу таблицу для хранения лайков/голосов.

Введите агрегации (Likes)

Создайте новый файл в components с именем

import React, { useState } from "react";
import { gql, useSubscription, useMutation } from "@apollo/client";

const likes = (movie_id) => gql`
  subscription {
    likes(where: {movie_id: {_eq: "${movie_id}"}}) {
        id
        user_id
    }
  }
`;

const LIKE = gql`
  mutation like($movie_id: uuid!) {
    insert_likes(objects: {movie_id: $movie_id}) {
        affected_rows
    }
  }
`;

const UNLIKE = gql`
    mutation unlike($movie_id: uuid!) {
        delete_likes(where: {movie_id: {_eq: $movie_id}}) {
            affected_rows
        }
    }
`;

function Component({movie}) {
  const LIKE_COUNT = likes(movie.id);
  const [addLike, { like_data, like_loading, error }] = useMutation(LIKE);
  const [unLike, _] = useMutation(UNLIKE);

  const [isRed, setIsRed] = useState(false);
  const { data, loading } = useSubscription(LIKE_COUNT);
  console.log(data, movie);
  if (loading) {
    return <div>Loading</div>;
  }
  const likeThis = () => {
    setIsRed(!isRed);
    if (isRed) {
        unLike({variables: {movie_id: movie.id}});
    } else {
        addLike({ variables: { movie_id: movie.id }});
    }
  }
  return (
    <span>
        {data.likes.length}
        <i className="fa fa-heart" style={{"color": isRed ? "red" : "gray"}} aria-hidden="true" onClick={likeThis}></i>
    </span>
  );
}

export default Component;

Войдите в полноэкранный режим Выйти из полноэкранного режима

О, подождите! Это приведет к удалению всех лайков в таблице. Поэтому давайте защитим нашу таблицу:

И импортируем это в App.js вот так:

import './App.css';
import { gql, useSubscription } from "@apollo/client";
import LikeCountComponent from "./components/likeCount";

const GET_MOVIES = gql`
  subscription {
    movies {
      id
      created_at
      name
      image
    }
  }
`;



function App() {
  const { data, loading } = useSubscription(GET_MOVIES);
  if (loading) {
    return <div>Loading</div>;
  }
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        data && data.movies && data.movies.length ?
        data.movies.map((movie, index) => {
          return (
              <div className="movie-box" key={index}>
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.image} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><LikeCountComponent movie={movie} /></div>
                </div>
              </div>
          )
        }) : "No movies"
      }
    </div>
  );
}

export default App;
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все! Поздравляем, вы только что создали приложение для голосования в кино.

Оцените статью
devanswers.ru
Добавить комментарий