Интеграция Firebase с приложением Next.js

1. Подготовка

В этой практической работе вы узнаете, как интегрировать Firebase с веб-приложением Next.js под названием Friendly Eats, которое представляет собой сайт с отзывами о ресторанах.

Веб-приложение Friendly Eats

Готовое веб-приложение предлагает полезные функции, которые демонстрируют, как Firebase может помочь вам создавать приложения Next.js. К ним относятся:

  • Автоматическая сборка и развертывание. В этой практической работе используется Firebase App Hosting для автоматической сборки и развертывания кода Next.js при каждой отправке в настроенную ветку.
  • Вход и выход. В готовом веб-приложении можно войти с аккаунтом Google и выйти из него. Вход пользователей и сохранение данных полностью управляются через Firebase Authentication.
  • Изображения. В готовом веб-приложении пользователи, выполнившие вход, могут загружать изображения ресторанов. Объекты изображений хранятся в Cloud Storage for Firebase. Firebase JavaScript SDK предоставляет общедоступный URL для загруженных изображений. Затем этот общедоступный URL сохраняется в соответствующем документе ресторана в Cloud Firestore.
  • Отзывы. В готовом веб-приложении пользователи, вошедшие в аккаунт, могут оставлять отзывы о ресторанах, состоящие из оценки и текстового сообщения. Информация об отзывах хранится в Cloud Firestore.
  • Фильтры. Готовое веб-приложение позволяет вошедшим в систему пользователям фильтровать список ресторанов по категории, местоположению и цене. Вы также можете настроить метод сортировки. Доступ к данным осуществляется из Cloud Firestore, а запросы Firestore применяются на основе используемых фильтров.

Требования

  • аккаунт GitHub;
  • Знание Next.js и JavaScript.

Функции, которые вы освоите:

Что вам понадобится

  • Git
  • Последняя стабильная версия Node.js.
  • Браузер, например Google Chrome.
  • Среда разработки с редактором кода и терминалом.
  • Аккаунт Google для создания проекта Firebase и управления им.
  • Возможность перейти на тарифный план Blaze в проекте Firebase.

2. Как настроить среду разработки и хранилище GitHub

В этой практической работе используется начальная база кода приложения и интерфейс командной строки Firebase.

Создайте хранилище GitHub

Исходный код практической работы можно найти на странице https://github.com/firebase/friendlyeats-web. В репозитории содержатся примеры проектов для нескольких платформ. Однако в этой практической работе используется только каталог nextjs-start. Обратите внимание на следующие каталоги:

* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.

Скопируйте папку nextjs-start в свое хранилище:

  1. В терминале создайте на компьютере новую папку и перейдите в нее:
    mkdir codelab-friendlyeats-web
    
    cd codelab-friendlyeats-web
    
  2. Используйте пакет npm giget, чтобы получить только папку nextjs-start:
    npx giget@latest "gh:firebase/friendlyeats-web/nextjs-start#master" . --install
    
  3. Отслеживайте изменения локально с помощью git:
    git init
    
    git add .
    
    git commit -m "Codelab starting point"
    
    git branch -M main
    
  4. Создайте новый репозиторий GitHub: https://github.com/new. Название может быть любым.
  5. В зависимости от того, как вы выполняете аутентификацию в GitHub (HTTPS или SSH), скопируйте новый URL, созданный GitHub:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git или
    • git@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
  6. Отправьте локальные изменения в новое хранилище GitHub, выполнив следующую команду: Замените плейсхолдер <REPOSITORY_URL> на URL своего хранилища.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. Теперь вы должны увидеть заготовку кода в своем репозитории GitHub.

Установите или обновите интерфейс командной строки Firebase

Выполните следующую команду, чтобы проверить, установлен ли у вас интерфейс командной строки Firebase и соответствует ли он версии 14.1.0 или более поздней:

firebase --version

Если у вас более ранняя версия или интерфейс командной строки Firebase не установлен, выполните команду установки:

npm install -g firebase-tools@latest

Если вам не удается установить интерфейс командной строки Firebase из-за ошибок, связанных с разрешениями, ознакомьтесь с документацией по npm или воспользуйтесь другим вариантом установки.

Войдите в Firebase.

  1. Чтобы войти в интерфейс командной строки Firebase, выполните следующую команду:
    firebase login
    
  2. В зависимости от того, хотите ли вы, чтобы Firebase собирал данные, введите Y или N.
  3. В браузере выберите аккаунт Google и нажмите Разрешить.

3. Как настроить проект Firebase

В этом разделе вы настроите проект Firebase и свяжете с ним веб-приложение Firebase. Вы также настроите сервисы Firebase, используемые примером веб-приложения.

Создать проект Firebase

  1. Войдите в консоль Firebase, используя тот же аккаунт Google, что и на предыдущем шаге.
  2. Нажмите кнопку, чтобы создать проект, и введите его название, например FriendlyEats Codelab.
  3. Нажмите Продолжить.
  4. Если появится запрос, прочитайте и примите Условия использования Firebase, а затем нажмите Продолжить.
  5. (Необязательно.) Включите помощь от ИИ в консоли Firebase (Gemini в Firebase).
  6. Для этой практической работы Google Аналитика не нужна, поэтому отключите ее.
  7. Нажмите Создать проект, дождитесь его инициализации и нажмите Продолжить.

Как перейти на другой тарифный план Firebase

Чтобы использовать Firebase App Hosting и Cloud Storage for Firebase, ваш проект Firebase должен быть на тарифном плане Blaze с оплатой по мере использования, то есть связан с платежным аккаунтом Cloud.

Чтобы перейти на тарифный план Blaze, выполните следующие действия:

  1. В консоли Firebase выберите смену тарифного плана.
  2. Выберите тарифный план Blaze. Следуйте инструкциям на экране, чтобы связать платежный аккаунт Cloud со своим проектом.
    Если в рамках этого обновления вам нужно было создать платежный аккаунт Cloud, возможно, вам потребуется вернуться к процессу обновления в консоли Firebase, чтобы завершить его.

Как добавить веб-приложение в проект Firebase

  1. В проекте Firebase перейдите в раздел Обзор проекта, нажмите Добавить приложение и выберите Веб.
  2. В текстовом поле Псевдоним приложения введите запоминающееся название, например My Next.js app.
  3. Не устанавливайте флажок Также настроить Firebase Hosting для этого приложения.
  4. Нажмите Register app (Зарегистрировать приложение) > Continue to console (Перейти в консоль).

Как настроить сервисы Firebase в консоли Firebase

Настроить аутентификацию

  1. На панели слева в консоли Firebase разверните раздел Build (Сборка) и выберите Authentication (Аутентификация).
  2. Нажмите Начать.
  3. В столбце Sign-in providers (Поставщики услуг входа) нажмите Google > Enable (Включить).
  4. В текстовом поле Общедоступное название проекта введите запоминающееся название, например My Next.js app.
  5. В раскрывающемся списке Support email for project (Адрес электронной почты службы поддержки для проекта) выберите свой адрес электронной почты.
  6. Нажмите Сохранить.

Как настроить Cloud Firestore

  1. На панели слева в консоли Firebase разверните раздел Build (Сборка) и выберите Firestore Database (База данных Firestore).
  2. Нажмите Создать базу данных.
  3. Выберите Стандартная версия и нажмите Далее.
  4. Не меняйте идентификатор базы данных, оставьте значение (default).
  5. Выберите местоположение базы данных и нажмите Далее.
    Для реального приложения лучше выбрать местоположение, которое находится ближе к пользователям.
  6. Нажмите Начать в тестовом режиме. Прочитайте отказ от обязательств, касающийся правил безопасности.
    Позже в этой практической работе вы добавите правила безопасности, чтобы защитить свои данные. Не распространяйте и не публикуйте приложение, не добавив правила безопасности для базы данных.
  7. Нажмите Создать.

Как настроить Cloud Storage for Firebase

  1. На панели слева в консоли Firebase разверните раздел Build (Сборка) и выберите Storage (Хранилище).
  2. Нажмите Начать.
  3. Выберите местоположение для сегмента хранилища по умолчанию.
    Для сегментов в регионах US-WEST1, US-CENTRAL1 и US-EAST1 можно использовать бесплатный уровень Google Cloud Storage. Для сегментов во всех остальных местоположениях действуют тарифы и условия использования Google Cloud Storage.
  4. Нажмите Начать в тестовом режиме. Прочитайте отказ от обязательств, касающийся правил безопасности.
    Позже в этой практической работе вы добавите правила безопасности, чтобы защитить свои данные. Не распространяйте и не публикуйте приложение, не добавив правила безопасности для сегмента хранилища.
  5. Нажмите Создать.

Развертывание правил безопасности

В коде уже есть наборы правил безопасности для Firestore и Cloud Storage for Firebase. После развертывания правил безопасности данные в базе данных и сегменте будут лучше защищены от ненадлежащего использования.

  1. В терминале настройте интерфейс командной строки для использования проекта Firebase, созданного ранее:
    firebase use --add
    
    Когда появится запрос на ввод псевдонима, введите friendlyeats-codelab.
  2. Чтобы развернуть эти правила безопасности (а также индексы, которые понадобятся позже), выполните в терминале следующую команду:
    firebase deploy --only firestore,storage
    
  3. Если появится вопрос "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?", нажмите Enter, чтобы выбрать Да.

4. Изучите начальную базу кода

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

Структура папок и файлов

В таблице ниже приведен обзор структуры папок и файлов приложения.

Файлы и папки

Описание

src/components

Компоненты React для фильтров, заголовков, сведений о ресторанах и отзывов

src/lib

Служебные функции, которые не обязательно привязаны к React или Next.js.

src/lib/firebase

Код и конфигурация Firebase

public

Статические объекты в веб-приложении, например значки.

src/app

Маршрутизация с помощью Next.js App Router

package.json и package-lock.json

Зависимости проекта с npm

next.config.js

Конфигурация для Next.js (действия сервера включены)

jsconfig.json

Конфигурация языкового сервиса JavaScript

Серверные и клиентские компоненты

Это веб-приложение Next.js, в котором используется App Router. Отрисовка на стороне сервера используется во всем приложении. Например, файл src/app/page.js – это компонент сервера, отвечающий за главную страницу. Файл src/components/RestaurantListings.jsx – это клиентский компонент, обозначенный директивой "use client" в начале файла.

Операторы import

Вы можете увидеть инструкции импорта, подобные следующим:

import RatingPicker from "@/src/components/RatingPicker.jsx";

В приложении используется символ @, чтобы избежать громоздких относительных путей импорта. Это возможно благодаря псевдонимам путей.

API, относящиеся к Firebase

Весь код Firebase API находится в каталоге src/lib/firebase. Затем отдельные компоненты React импортируют упакованные функции из каталога src/lib/firebase, а не функции Firebase напрямую.

Фиктивные данные

Файл src/lib/randomData.js содержит макеты данных о ресторанах и отзывах. Данные из этого файла собираются в коде в файле src/lib/fakeRestaurants.js.

5. Как создать бэкенд App Hosting

В этом разделе вы настроите серверную часть App Hosting для отслеживания ветки в репозитории git.

К концу этого раздела у вас будет серверная часть App Hosting, подключенная к вашему репозиторию в GitHub, которая будет автоматически пересобирать и развертывать новую версию вашего приложения всякий раз, когда вы отправляете новый коммит в свою ветку main.

Создание серверной ВМ

  1. В консоли Firebase перейдите на страницу App Hosting.
  2. Нажмите Начать, чтобы запустить процесс создания бэкенда.
  3. Выберите регион. Для реального приложения выберите регион, ближайший к вашим пользователям.
  4. Следуйте инструкциям на шаге Импорт репозитория GitHub, чтобы настроить аутентификацию GitHub.
  5. В разделе Repository (Хранилище) выберите Grant access to a new repository in GitHub (Предоставить доступ к новому хранилищу в GitHub) и следуйте инструкциям, чтобы разрешить доступ к хранилищу GitHub, которое вы создали ранее.
  6. Нажмите Обновить список, выберите хранилище и нажмите Далее.
  7. Задайте настройки развертывания:
    1. Установите для активной ветви значение main.
    2. Оставьте корневой каталог /.
    3. Включите автоматическое развертывание.
  8. Укажите название бэкенда friendlyeats-codelab и нажмите Далее.
  9. В разделе Связать веб-приложение Firebase выберите Выбрать существующее веб-приложение Firebase и укажите добавленное приложение из списка.
  10. Нажмите Завершить и развернуть. Вы будете перенаправлены на новую страницу, где можно посмотреть статус нового бэкенда App Hosting.
  11. Нажмите View (Посмотреть), чтобы узнать больше о развертывании App Hosting, в том числе о статусе внедрения, журналах и сведениях об использовании.
  12. После завершения развертывания нажмите на URL сайта в разделе Domains (Домены). Из-за распространения DNS изменения могут вступить в силу через несколько минут.
  13. Ой-ой! При загрузке страницы вы увидите сообщение об ошибке: "Ошибка приложения: возникло исключение на стороне сервера (дополнительную информацию см. в журналах сервера)".
  14. В консоли Firebase откройте вкладку Logs (Журналы) для серверной части App Hosting. Вы увидите запись в журнале "Error: not implemented" (Ошибка: не реализовано). Мы исправим это на следующем шаге, когда добавим аутентификацию.

Вы развернули начальное веб-приложение. Каждый раз, когда вы отправляете новый коммит в ветку main репозитория GitHub, в консоли Firebase будет начинаться новая сборка и развертывание, а ваш сайт будет автоматически обновляться после завершения развертывания.

6. Добавление аутентификации в веб-приложение

В этом разделе вы добавите в веб-приложение аутентификацию, чтобы в него можно было войти.

Добавьте авторизованный домен.

Firebase Authentication будет принимать запросы на вход только из разрешенных доменов. Здесь мы добавим домен бэкенда App Hosting в список одобренных доменов в вашем проекте.

  1. Откройте страницу App Hosting и нажмите View (Посмотреть) под развернутым сайтом, чтобы перейти на страницу Overview (Обзор). Скопируйте доменное имя бэкенда App Hosting.
  2. Перейдите на вкладку Auth Settings (Настройки аутентификации) и выберите проект, в который нужно добавить авторизованный домен. Затем найдите раздел Authorized Domains (Авторизованные домены) и нажмите на него.
  3. Нажмите кнопку Добавить домен.
  4. Введите домен бэкенда App Hosting.
  5. Нажмите Добавить.

Реализуйте функции входа и выхода

В файле src/lib/firebase/auth.js замените функции onAuthStateChanged, onIdTokenChanged, signInWithGoogle и signOut следующим кодом:

export function onAuthStateChanged(cb) {
  return _onAuthStateChanged(auth, cb);
}

export function onIdTokenChanged(cb) {
  return _onIdTokenChanged(auth, cb);
}

export async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();

  try {
    await signInWithPopup(auth, provider);
  } catch (error) {
    console.error("Error signing in with Google", error);
  }
}

export async function signOut() {
  try {
    return auth.signOut();
  } catch (error) {
    console.error("Error signing out with Google", error);
  }
}

В этом коде используются следующие API Firebase:

Firebase API

Описание

auth.onAuthStateChanged

Добавляет наблюдателя за изменениями статуса входа пользователя.

auth.onIdTokenChanged

Добавляет наблюдателя за изменениями токена идентификатора пользователя.

GoogleAuthProvider

Создает экземпляр поставщика аутентификации Google.

signInWithPopup

Запускает процесс аутентификации на основе диалогового окна.

auth.signOut

Выход из аккаунта.

В файле src/components/Header.jsx код уже вызывает функции signInWithGoogle и signOut.

Отправка статуса аутентификации на сервер

Чтобы передать статус аутентификации на сервер, мы будем использовать файлы cookie. При каждом изменении статуса аутентификации в клиенте мы будем обновлять файл cookie __session.

В файле src/components/Header.jsx замените функцию useUserSession следующим кодом:

function useUserSession(initialUser) {
  useEffect(() => {
    return onIdTokenChanged(async (user) => {
      if (user) {
        const idToken = await user.getIdToken();
        await setCookie("__session", idToken);
      } else {
        await deleteCookie("__session");
      }
      if (initialUser?.uid === user?.uid) {
        return;
      }
      window.location.reload();
    });
  }, [initialUser]);

  return initialUser;
}

Чтение состояния аутентификации на сервере

Мы будем использовать FirebaseServerApp, чтобы отразить статус аутентификации клиента на сервере.

Откройте файл src/lib/firebase/serverApp.js и замените функцию getAuthenticatedAppForUser:

export async function getAuthenticatedAppForUser() {
  const authIdToken = (await cookies()).get("__session")?.value;

  // Firebase Server App is a new feature in the JS SDK that allows you to
  // instantiate the SDK with credentials retrieved from the client & has
  // other affordances for use in server environments.
  const firebaseServerApp = initializeServerApp(
    // https://github.com/firebase/firebase-js-sdk/issues/8863#issuecomment-2751401913
    initializeApp(),
    {
      authIdToken,
    }
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

Подтвердить изменения

Корневой макет в файле src/app/layout.js отображает заголовок и передает пользователя, если он доступен, в качестве свойства.

<Header initialUser={currentUser?.toJSON()} />

Это означает, что компонент <Header> отображает пользовательские данные, если они доступны, во время работы сервера. Если в течение жизненного цикла страницы после ее первоначальной загрузки происходят какие-либо обновления аутентификации, их обрабатывает обработчик onAuthStateChanged.

Теперь нужно выпустить новую сборку и проверить, что у вас получилось.

  1. Создайте коммит с сообщением "Add authentication" и отправьте его в репозиторий GitHub.
    git add .
    
    git commit -m "Add authentication"
    
    git push
    
  2. Откройте страницу App Hosting и, когда развертывание будет завершено, нажмите на URL сайта, чтобы открыть его.
  3. Проверьте аутентификацию:
    1. Войдите в аккаунт Google и убедитесь, что ваше отображаемое имя появилось в заголовке.
    2. Выйдите и снова войдите в аккаунт. Вы можете повторить этот шаг для разных пользователей.
    3. При необходимости нажмите правой кнопкой мыши на веб-приложение, выберите Просмотр кода страницы и найдите отображаемое название. Он присутствует в исходном HTML-коде, полученном от сервера.

7. Посмотреть информацию о ресторане

Веб-приложение включает фиктивные данные для ресторанов и отзывов.

Добавьте один или несколько ресторанов

Чтобы вставить макет данных ресторана в локальную базу данных Cloud Firestore, выполните следующие действия:

  1. Войдите в веб-приложение, если вы ещё этого не сделали. Затем нажмите 2cf67d488d8e6332.png> Добавить примеры ресторанов. Обратите внимание, что в веб-приложении Friendly Eats не отображаются рестораны, поскольку мы ещё не настроили код получения данных. Мы исправим это на следующем шаге.
  2. В консоли Firebase на странице База данных Firestore выберите restaurants. Вы увидите документы верхнего уровня в коллекции ресторанов, каждый из которых представляет ресторан.
  3. Нажмите на несколько документов, чтобы изучить свойства документа ресторана.

Показать список ресторанов

Теперь в базе данных Cloud Firestore есть рестораны, которые может показывать веб-приложение Next.js.

Чтобы определить код для получения данных, выполните следующие действия:

  1. В файле src/app/page.js найдите серверный компонент <Home /> и проверьте вызов функции getRestaurants, которая получает список ресторанов во время работы сервера. Функция getRestaurants реализуется следующим образом:
  2. В файле src/lib/firebase/firestore.js замените функции applyQueryFilters и getRestaurants следующим кодом:
function applyQueryFilters(q, { category, city, price, sort }) {
  if (category) {
    q = query(q, where("category", "==", category));
  }
  if (city) {
    q = query(q, where("city", "==", city));
  }
  if (price) {
    q = query(q, where("price", "==", price.length));
  }
  if (sort === "Rating" || !sort) {
    q = query(q, orderBy("avgRating", "desc"));
  } else if (sort === "Review") {
    q = query(q, orderBy("numRatings", "desc"));
  }
  return q;
}

export async function getRestaurants(db = db, filters = {}) {
  let q = query(collection(db, "restaurants"));

  q = applyQueryFilters(q, filters);
  const results = await getDocs(q);
  return results.docs.map((doc) => {
    return {
      id: doc.id,
      ...doc.data(),
      // Only plain objects can be passed to Client Components from Server Components
      timestamp: doc.data().timestamp.toDate(),
    };
  });
}
  1. Создайте коммит с сообщением "Read the list of restaurants from Firestore" (Прочитать список ресторанов из Firestore) и отправьте его в хранилище GitHub.
    git add .
    
    git commit -m "Read the list of restaurants from Firestore"
    
    git push
    
  2. Откройте страницу App Hosting в консоли Firebase и дождитесь завершения развертывания.
  3. Обновите страницу в веб-приложении. Изображения ресторана показаны в виде плиток на странице.

Убедитесь, что данные о ресторанах загружаются во время работы сервера.

При использовании фреймворка Next.js может быть не очевидно, когда данные загружаются во время выполнения на сервере или на клиентской стороне.

Чтобы убедиться, что данные о ресторанах загружаются во время работы сервера, выполните следующие действия:

  1. В веб-приложении откройте Инструменты разработчика и отключите JavaScript.

Как отключить JavaScript в инструментах разработчика

  1. Обновите веб-приложение. Список ресторанов по-прежнему загружается. Информация о ресторане возвращается в ответе сервера. Если JavaScript включен, информация о ресторане гидратируется с помощью клиентского кода JavaScript.
  2. В инструментах разработчика снова включите JavaScript.

Прослушивание обновлений ресторанов с помощью прослушивателей снимков Cloud Firestore

В предыдущем разделе вы увидели, как из файла src/app/page.js загружается исходный набор ресторанов. Файл src/app/page.js – это серверный компонент, который обрабатывается на сервере, включая код Firebase для получения данных.

Файл src/components/RestaurantListings.jsx – это клиентский компонент, который можно настроить для гидратации разметки, созданной сервером.

Чтобы настроить файл src/components/RestaurantListings.jsx для гидратации разметки, созданной на сервере, выполните следующие действия:

  1. В файле src/components/RestaurantListings.jsx найдите следующий код:
useEffect(() => {
    return getRestaurantsSnapshot((data) => {
      setRestaurants(data);
    }, filters);
  }, [filters]);

Этот код вызывает функцию getRestaurantsSnapshot(), которая похожа на функцию getRestaurants(), реализованную на предыдущем шаге. Однако эта функция snapshot предоставляет механизм обратного вызова, поэтому обратный вызов вызывается каждый раз, когда в коллекцию ресторана вносятся изменения.

  1. В файле src/lib/firebase/firestore.js замените функцию getRestaurantsSnapshot() следующим кодом:
export function getRestaurantsSnapshot(cb, filters = {}) {
  if (typeof cb !== "function") {
    console.log("Error: The callback parameter is not a function");
    return;
  }

  let q = query(collection(db, "restaurants"));
  q = applyQueryFilters(q, filters);

  return onSnapshot(q, (querySnapshot) => {
    const results = querySnapshot.docs.map((doc) => {
      return {
        id: doc.id,
        ...doc.data(),
        // Only plain objects can be passed to Client Components from Server Components
        timestamp: doc.data().timestamp.toDate(),
      };
    });

    cb(results);
  });
}
  1. В файле src/lib/firebase/firestore.js замените функцию getRestaurantSnapshotById() следующим кодом:
export function getRestaurantSnapshotById(restaurantId, cb) {
  if (!restaurantId) {
    console.log("Error: Invalid ID received: ", restaurantId);
    return;
  }

  if (typeof cb !== "function") {
    console.log("Error: The callback parameter is not a function");
    return;
  }

  const docRef = doc(db, "restaurants", restaurantId);
  return onSnapshot(docRef, (docSnap) => {
    cb({
      ...docSnap.data(),
      timestamp: docSnap.data().timestamp.toDate(),
    });
  });
}

Изменения, внесенные на странице База данных Firestore, теперь отражаются в веб-приложении в реальном времени.

  1. Создайте коммит с сообщением "Listen for realtime restaurant updates" и отправьте его в репозиторий GitHub.
    git add .
    
    git commit -m "Listen for realtime restaurant updates"
    
    git push
    
  2. Откройте страницу App Hosting в консоли Firebase и дождитесь завершения развертывания.
  3. В веб-приложении нажмите 27ca5d1e8ed8adfe.png > Добавить примеры ресторанов. Если функция snapshot реализована правильно, рестораны появляются в режиме реального времени без обновления страницы.

8. Сохранять отзывы, отправленные пользователями из веб-приложения

  1. В файле src/lib/firebase/firestore.js замените функцию updateWithRating() следующим кодом:
const updateWithRating = async (
  transaction,
  docRef,
  newRatingDocument,
  review
) => {
  const restaurant = await transaction.get(docRef);
  const data = restaurant.data();
  const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1;
  const newSumRating = (data?.sumRating || 0) + Number(review.rating);
  const newAverage = newSumRating / newNumRatings;

  transaction.update(docRef, {
    numRatings: newNumRatings,
    sumRating: newSumRating,
    avgRating: newAverage,
  });

  transaction.set(newRatingDocument, {
    ...review,
    timestamp: Timestamp.fromDate(new Date()),
  });
};

Этот код вставляет новый документ Firestore, представляющий новый отзыв. Код также обновляет существующий документ Firestore, который представляет ресторан, с обновленными цифрами количества оценок и средней рассчитанной оценки.

  1. Замените функцию addReviewToRestaurant() следующим кодом:
export async function addReviewToRestaurant(db, restaurantId, review) {
  if (!restaurantId) {
    throw new Error("No restaurant ID has been provided.");
  }

  if (!review) {
    throw new Error("A valid review has not been provided.");
  }

  try {
    const docRef = doc(collection(db, "restaurants"), restaurantId);
    const newRatingDocument = doc(
      collection(db, `restaurants/${restaurantId}/ratings`),
    );

    // corrected line
    await runTransaction(db, (transaction) =>
      updateWithRating(transaction, docRef, newRatingDocument, review),
    );
  } catch (error) {
    console.error(
      "There was an error adding the rating to the restaurant",
      error,
    );
    throw error;
  }
}

Реализация действия сервера Next.js

Next.js Server Action предоставляет удобный API для доступа к данным формы, например data.get("text") для получения текстового значения из полезной нагрузки отправки формы.

Чтобы использовать действие сервера Next.js для обработки отправки формы отзыва, выполните следующие действия:

  1. В файле src/components/ReviewDialog.jsx найдите атрибут action в элементе <form>.
<form
  action={handleReviewFormSubmission}
  onSubmit={() => {
    handleClose();
  }}
>

Значение атрибута action относится к функции, которую вы реализуете на следующем шаге.

  1. В файле src/app/actions.js замените функцию handleReviewFormSubmission() следующим кодом:
export async function handleReviewFormSubmission(data) {
  const { firebaseServerApp } = await getAuthenticatedAppForUser();
  const db = getFirestore(firebaseServerApp);

  await addReviewToRestaurant(db, data.get("restaurantId"), {
    text: data.get("text"),
    rating: data.get("rating"),

    // This came from a hidden form field.
    userId: data.get("userId"),
  });
}

Добавьте отзывы о ресторане

Вы реализовали поддержку отправки отзывов, поэтому теперь вы можете убедиться, что ваши отзывы правильно вставляются в Cloud Firestore.

Чтобы добавить отзыв и убедиться, что он вставлен в Cloud Firestore, выполните следующие действия:

  1. Создайте коммит с сообщением "Allow users to submit restaurant reviews" и отправьте его в репозиторий GitHub.
    git add .
    
    git commit -m "Allow users to submit restaurant reviews"
    
    git push
    
  2. Откройте страницу App Hosting в консоли Firebase и дождитесь завершения развертывания.
  3. Обновите веб-приложение и выберите ресторан на главной странице.
  4. На странице ресторана нажмите 3e19beef78bb0d0e.png.
  5. Выберите оценку.
  6. Напишите отзыв.
  7. Нажмите Отправить. Ваш отзыв появится вверху списка.
  8. В Cloud Firestore на панели Add document (Добавить документ) найдите и выберите документ ресторана, для которого вы оставили отзыв.
  9. На панели Начать сбор выберите Оценки.
  10. На панели Добавить документ найдите документ, который вы хотите проверить, и убедитесь, что он был вставлен правильно.

9. Сохраните файлы, загруженные пользователями из веб-приложения

В этом разделе вы добавите функцию, которая позволит заменять изображение, связанное с рестораном, когда вы вошли в систему. Вы загружаете изображение в Firebase Storage и обновляете URL изображения в документе Cloud Firestore, который представляет ресторан.

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

  1. В файле src/components/Restaurant.jsx найдите код, который выполняется, когда пользователь загружает файл:
async function handleRestaurantImage(target) {
  const image = target.files ? target.files[0] : null;
  if (!image) {
    return;
  }

  const imageURL = await updateRestaurantImage(id, image);
  setRestaurantDetails({ ...restaurantDetails, photo: imageURL });
}

Вносить изменения в эту функцию не нужно, но на следующих шагах вы реализуете поведение функции updateRestaurantImage().

  1. В файле src/lib/firebase/storage.js замените функции updateRestaurantImage() и uploadImage() следующим кодом:
export async function updateRestaurantImage(restaurantId, image) {
  try {
    if (!restaurantId) {
      throw new Error("No restaurant ID has been provided.");
    }

    if (!image || !image.name) {
      throw new Error("A valid image has not been provided.");
    }

    const publicImageUrl = await uploadImage(restaurantId, image);
    await updateRestaurantImageReference(restaurantId, publicImageUrl);

    return publicImageUrl;
  } catch (error) {
    console.error("Error processing request:", error);
  }
}

async function uploadImage(restaurantId, image) {
  const filePath = `images/${restaurantId}/${image.name}`;
  const newImageRef = ref(storage, filePath);
  await uploadBytesResumable(newImageRef, image);

  return await getDownloadURL(newImageRef);
}

Функция updateRestaurantImageReference() уже реализована. Эта функция обновляет существующий документ ресторана в Cloud Firestore, добавляя в него обновленный URL изображения.

Проверьте функцию загрузки изображений

Чтобы убедиться, что изображение загружается правильно, выполните следующие действия:

  1. Создайте коммит с сообщением "Allow users to change each restaurants' photo" и отправьте его в репозиторий GitHub.
    git add .
    
    git commit -m "Allow users to change each restaurants' photo"
    
    git push
    
  2. Откройте страницу App Hosting в консоли Firebase и дождитесь завершения развертывания.
  3. В веб-приложении убедитесь, что вы вошли в аккаунт, и выберите ресторан.
  4. Нажмите 7067eb41fea41ff0.png и загрузите изображение из файловой системы. Образ покидает локальную среду и загружается в Cloud Storage. Изображение появится сразу после загрузки.
  5. Перейдите в Cloud Storage для Firebase.
  6. Перейдите в папку, которая представляет ресторан. Загруженное изображение находится в папке.

6cf3f9e2303c931c.png

10. Краткий пересказ отзывов о ресторане с помощью генеративного искусственного интеллекта

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

Как сохранить ключ Gemini API в Cloud Secret Manager

  1. Чтобы использовать Gemini API, вам понадобится ключ API. Перейдите в Google AI Studio и нажмите Create API Key (Создать ключ API).
  2. Укажите любое название ключа. Если вашего проекта нет в списке Выберите импортированный проект, нажмите Импортировать проект, выберите проект в списке и нажмите Импортировать. Затем выберите его в разделе Выберите импортированный проект и нажмите Создать ключ.
  3. App Hosting интегрируется с Cloud Secret Manager, что позволяет безопасно хранить конфиденциальные данные, например ключи API.
    1. В терминале выполните команду, чтобы создать секрет:
    firebase apphosting:secrets:set GEMINI_API_KEY
    
    1. Когда появится запрос на ввод секретного значения, скопируйте и вставьте ключ Gemini API из Google AI Studio.
    2. Когда появится вопрос о том, для чего предназначен новый секретный ключ, выберите "Рабочая версия".
    3. Когда появится запрос на предоставление доступа к секрету сервисному аккаунту серверной части, выберите "Да".
    4. Когда появится запрос на добавление нового секрета в apphosting.yaml, введите Y, чтобы принять.

Теперь ваш ключ Gemini API надежно хранится в Cloud Secret Manager и доступен для серверной части App Hosting.

Как реализовать компонент сводки отзывов

  1. В файле src/components/Reviews/ReviewSummary.jsx замените функцию GeminiSummary следующим кодом:
    export async function GeminiSummary({ restaurantId }) {
      const { firebaseServerApp } = await getAuthenticatedAppForUser();
      const reviews = await getReviewsByRestaurantId(
        getFirestore(firebaseServerApp),
        restaurantId
      );
    
      const reviewSeparator = "@";
      const prompt = `
        Based on the following restaurant reviews, 
        where each review is separated by a '${reviewSeparator}' character, 
        create a one-sentence summary of what people think of the restaurant. 
    
        Here are the reviews: ${reviews.map((review) => review.text).join(reviewSeparator)}
      `;
    
      try {
        if (!process.env.GEMINI_API_KEY) {
          // Make sure GEMINI_API_KEY environment variable is set:
          // https://genkit.dev/docs/get-started/
          throw new Error(
            'GEMINI_API_KEY not set. Set it with "firebase apphosting:secrets:set GEMINI_API_KEY"'
          );
        }
    
        // Configure a Genkit instance.
        const ai = genkit({
          plugins: [googleAI()],
          model: gemini20Flash, // set default model
        });
        const { text } = await ai.generate(prompt);
    
        return (
          <div className="restaurant__review_summary">
            <p>{text}</p>
            <p>✨ Summarized with Gemini</p>
          </div>
        );
      } catch (e) {
        console.error(e);
        return <p>Error summarizing reviews.</p>;
      }
    }
    
  2. Создайте коммит с сообщением "Use AI to summarize reviews" и отправьте его в репозиторий GitHub.
    git add .
    
    git commit -m "Use AI to summarize reviews"
    
    git push
    
  3. Откройте страницу App Hosting в консоли Firebase и дождитесь завершения развертывания.
  4. Откройте страницу ресторана. В верхней части страницы вы увидите краткий пересказ всех отзывов.
  5. Добавьте новый отзыв и обновите страницу. Вы увидите, что сводка изменилась.

11. Как отменить публикацию сайта, опубликованного с помощью App Hosting

Если вы не планируете использовать приложение после завершения этой практической работы, вы можете отменить его публикацию, чтобы никто не мог получить доступ к вашим ресурсам Firestore, Storage и Gemini. Вы можете в любое время опубликовать издание снова.

Чтобы отменить публикацию сайта, размещенного с помощью App Hosting, выполните следующие действия:

  1. Откройте App Hosting в консоли Firebase.
  2. Найдите серверную часть приложения и нажмите View (Посмотреть).
  3. В разделе Информация о серверной части рядом с пунктом Домены нажмите Управление. Откроется страница Domains (Домены).
  4. Рядом с доменом нажмите на значок Ещё (три вертикальные точки), выберите Отключить домен, а затем нажмите Отключить для подтверждения.

12. Заключение

Поздравляем! Вы узнали, как использовать Firebase для добавления функций и возможностей в приложение Next.js. В частности, вы использовали следующее:

Подробнее