Веб-лаборатория Cloud Firestore

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

1. Обзор

Цели

В этой лаборатории кода вы создадите веб-приложение для рекомендаций ресторанов на базе Cloud Firestore .

img5.png

Что вы узнаете

  • Чтение и запись данных в Cloud Firestore из веб-приложения
  • Слушайте изменения в данных Cloud Firestore в режиме реального времени
  • Используйте Firebase Authentication и правила безопасности для защиты данных Cloud Firestore.
  • Написание сложных запросов Cloud Firestore

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

Перед запуском этой кодлабы убедитесь, что вы установили:

  • npm , который обычно поставляется с Node.js — рекомендуется Node v8
  • IDE/текстовый редактор по вашему выбору, например WebStorm , Atom , VS Code или Sublime.

2. Создайте и настройте проект Firebase

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

  1. В консоли Firebase нажмите « Добавить проект» , затем назовите проект Firebase « FriendlyEats ».

Запомните идентификатор проекта для вашего проекта Firebase.

  1. Щелкните Создать проект .

Приложение, которое мы собираемся создать, использует несколько сервисов Firebase, доступных в Интернете:

  • Firebase Authentication для легкой идентификации ваших пользователей
  • Cloud Firestore для сохранения структурированных данных в облаке и получения мгновенных уведомлений при обновлении данных.
  • Хостинг Firebase для размещения и обслуживания ваших статических ресурсов

Для этой конкретной кодовой лаборатории мы уже настроили Firebase Hosting. Однако для Firebase Auth и Cloud Firestore мы проведем вас через настройку и включение служб с помощью консоли Firebase.

Включить анонимную аутентификацию

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

Вам нужно включить анонимный вход.

  1. В консоли Firebase найдите раздел « Сборка » в левой навигационной панели.
  2. Щелкните « Аутентификация » , затем щелкните вкладку « Метод входа » (или щелкните здесь , чтобы сразу перейти туда).
  3. Включите поставщика анонимного входа, затем нажмите « Сохранить » .

img7.png

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

Включить облачное хранилище Firestore

Приложение использует Cloud Firestore для сохранения и получения информации и рейтингов ресторанов.

Вам нужно включить Cloud Firestore. В разделе « Сборка » консоли Firebase нажмите « База данных Firestore ». Нажмите Создать базу данных на панели Cloud Firestore.

Доступ к данным в Cloud Firestore контролируется правилами безопасности. Мы поговорим о правилах позже в этой лаборатории кода, но сначала нам нужно установить некоторые основные правила для наших данных, чтобы начать. На вкладке «Правила» консоли Firebase добавьте следующие правила и нажмите « Опубликовать ».

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

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

3. Получите пример кода

Клонируйте репозиторий GitHub из командной строки:

git clone https://github.com/firebase/friendlyeats-web

Пример кода должен был быть клонирован в каталог 📁 friendlyeats-web . С этого момента обязательно запускайте все свои команды из этого каталога:

cd friendlyeats-web

Импортировать стартовое приложение

Используя IDE (WebStorm, Atom, Sublime, Visual Studio Code...), откройте или импортируйте каталог 📁 friendlyeats-web . Этот каталог содержит начальный код для кодовой лаборатории, которая состоит из еще не работающего приложения для рекомендаций ресторанов. Мы сделаем его функциональным во всей этой кодовой лаборатории, поэтому вскоре вам нужно будет отредактировать код в этом каталоге.

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

Интерфейс командной строки Firebase (CLI) позволяет вам обслуживать ваше веб-приложение локально и развертывать его на хостинге Firebase.

  1. Установите CLI, выполнив следующую команду npm:
npm -g install firebase-tools
  1. Убедитесь, что CLI установлен правильно, выполнив следующую команду:
firebase --version

Убедитесь, что версия интерфейса командной строки Firebase — 7.4.0 или более поздняя.

  1. Авторизуйте интерфейс командной строки Firebase, выполнив следующую команду:
firebase login

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

  1. Убедитесь, что ваша командная строка обращается к локальному каталогу вашего приложения.
  2. Свяжите свое приложение с проектом Firebase, выполнив следующую команду:
firebase use --add
  1. При появлении запроса выберите идентификатор проекта , а затем дайте псевдоним вашему проекту Firebase.

Псевдоним полезен, если у вас несколько сред (производственная, промежуточная и т. д.). Однако для этой кодлабы давайте просто воспользуемся псевдонимом default .

  1. Следуйте оставшимся инструкциям в вашей командной строке.

5. Запустите локальный сервер

Мы готовы начать работу над нашим приложением! Давайте запустим наше приложение локально!

  1. Запустите следующую команду Firebase CLI:
firebase emulators:start --only hosting
  1. Ваша командная строка должна отобразить следующий ответ:
hosting: Local server: http://localhost:5000

Мы используем эмулятор Firebase Hosting для локального обслуживания нашего приложения. Теперь веб-приложение должно быть доступно по адресу http://localhost:5000 .

  1. Откройте свое приложение по адресу http://localhost:5000 .

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

Приложение автоматически подключилось к вашему проекту Firebase и автоматически вошло в систему как анонимный пользователь.

img2.png

6. Запишите данные в Cloud Firestore

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

Модель данных

Данные Firestore разделены на коллекции, документы, поля и подколлекции. Мы будем хранить каждый ресторан как документ в коллекции верхнего уровня с именем restaurants .

img3.png

Позже мы будем хранить каждый отзыв в подколлекции под названием ratings под каждым рестораном.

img4.png

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

Основным объектом модели в нашем приложении является ресторан. Давайте напишем код, добавляющий документ restaurants в коллекцию ресторанов.

  1. В загруженных файлах откройте scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.addRestaurant .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

Приведенный выше код добавляет новый документ в коллекцию restaurants . Данные документа поступают из простого объекта JavaScript. Мы делаем это, сначала получая ссылку на restaurants коллекции Cloud Firestore, а затем add данные.

Добавим рестораны!

  1. Вернитесь в приложение FriendlyEats в браузере и обновите его.
  2. Щелкните Добавить фиктивные данные .

Приложение автоматически сгенерирует случайный набор объектов ресторанов, а затем вызовет вашу функцию addRestaurant . Однако вы еще не увидите данные в реальном веб-приложении, потому что нам все еще нужно реализовать извлечение данных (следующий раздел лаборатории кода).

Однако, если вы перейдете на вкладку Cloud Firestore в консоли Firebase, вы должны увидеть новые документы в коллекции restaurants !

img6.png

Поздравляем, вы только что записали данные в Cloud Firestore из веб-приложения!

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

7. Отображение данных из Cloud Firestore

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

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

  1. Вернитесь к файлу scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.getAllRestaurants .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

В приведенном выше коде мы создаем запрос, который извлечет до 50 ресторанов из коллекции верхнего уровня с именем restaurants , которые упорядочены по среднему рейтингу (в настоящее время все ноль). После того, как мы объявили этот запрос, мы передаем его getDocumentsInQuery() , который отвечает за загрузку и визуализацию данных.

Мы сделаем это, добавив прослушиватель моментальных снимков.

  1. Вернитесь к файлу scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.getDocumentsInQuery .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

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

  • В первый раз обратный вызов запускается со всем набором результатов запроса, то есть со всей коллекцией restaurants из Cloud Firestore. Затем он передает все отдельные документы функции renderer.display .
  • Когда документ removed , change.type равно remove. Итак, в этом случае мы вызовем функцию, которая удалит ресторан из пользовательского интерфейса.

Теперь, когда мы реализовали оба метода, обновите приложение и убедитесь, что рестораны, которые мы видели ранее в консоли Firebase, теперь видны в приложении. Если вы успешно завершили этот раздел, ваше приложение теперь читает и записывает данные с помощью Cloud Firestore!

По мере изменения вашего списка ресторанов этот слушатель будет автоматически обновляться. Попробуйте зайти в консоль Firebase и вручную удалить ресторан или изменить его название — вы увидите, что изменения немедленно отобразятся на вашем сайте!

img5.png

8. Получить() данные

До сих пор мы показали, как использовать onSnapshot для получения обновлений в режиме реального времени; однако это не всегда то, что нам нужно. Иногда имеет смысл получить данные только один раз.

Мы хотим реализовать метод, который срабатывает, когда пользователь переходит в определенный ресторан в вашем приложении.

  1. Вернитесь к своему файлу scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.getRestaurant .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

После того, как вы внедрили этот метод, вы сможете просматривать страницы каждого ресторана. Просто нажмите на ресторан в списке, и вы должны увидеть страницу сведений о ресторане:

img1.png

На данный момент вы не можете добавлять рейтинги, так как нам все еще нужно реализовать добавление рейтингов позже в лаборатории кода.

9. Сортировка и фильтрация данных

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

Вот пример простого запроса для получения всех ресторанов Dim Sum :

var filteredQuery = query.where('category', '==', 'Dim Sum')

Как следует из названия, метод where() заставит наш запрос загружать только элементы коллекции, чьи поля соответствуют установленным нами ограничениям. В этом случае он будет загружать только рестораны с category Dim Sum .

В нашем приложении пользователь может объединять несколько фильтров для создания конкретных запросов, таких как «Пицца в Сан-Франциско» или «Морепродукты в Лос-Анджелесе, заказанные по популярности».

Мы создадим метод, который создает запрос, который будет фильтровать наши рестораны на основе нескольких критериев, выбранных нашими пользователями.

  1. Вернитесь к своему файлу scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.getFilteredRestaurants .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

Приведенный выше код добавляет несколько фильтров where и одно предложение orderBy для создания составного запроса на основе пользовательского ввода. Теперь наш запрос будет возвращать только те рестораны, которые соответствуют требованиям пользователя.

Обновите приложение FriendlyEats в браузере и убедитесь, что вы можете фильтровать по цене, городу и категории. Во время тестирования вы увидите ошибки в консоли JavaScript вашего браузера, которые выглядят следующим образом:

The query requires an index. You can create it here: https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

Эти ошибки связаны с тем, что Cloud Firestore требует индексов для большинства составных запросов. Требование индексов для запросов позволяет Cloud Firestore быстро масштабироваться.

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

10. Разверните индексы

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

  1. В загруженном локальном каталоге вашего приложения вы найдете файл firestore.indexes.json .

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

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. Разверните эти индексы с помощью следующей команды:
firebase deploy --only firestore:indexes

Через несколько минут ваши индексы будут активны, а сообщения об ошибках исчезнут.

11. Запишите данные в транзакцию

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

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

К счастью, Cloud Firestore предоставляет функциональность транзакций, которая позволяет нам выполнять несколько операций чтения и записи в одной атомарной операции, гарантируя, что наши данные останутся согласованными.

  1. Вернитесь к своему файлу scripts/FriendlyEats.Data.js .
  2. Найдите функцию FriendlyEats.prototype.addRating .
  3. Замените всю функцию следующим кодом.

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

В приведенном выше блоке мы инициируем транзакцию для обновления числовых значений avgRating и numRatings в документе ресторана. В то же время мы добавляем новый rating в подколлекцию ratings .

12. Защитите свои данные

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

  1. В разделе « Сборка » консоли Firebase нажмите « База данных Firestore ».
  2. Перейдите на вкладку « Правила » в разделе Cloud Firestore (или щелкните здесь, чтобы перейти непосредственно туда).
  3. Замените значения по умолчанию следующими правилами, затем нажмите « Опубликовать » .

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data) 
      && (key in request.resource.data) 
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys()) 
                    && unchanged("name");
      
      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

Эти правила ограничивают доступ, чтобы клиенты могли вносить только безопасные изменения. Например:

  • Обновления документа ресторана могут изменить только рейтинги, но не название или любые другие неизменяемые данные.
  • Рейтинги могут быть созданы только в том случае, если идентификатор пользователя совпадает с вошедшим в систему пользователем, что предотвращает спуфинг.

В качестве альтернативы использованию консоли Firebase вы можете использовать интерфейс командной строки Firebase для развертывания правил в своем проекте Firebase. Файл firestore.rules в вашем рабочем каталоге уже содержит указанные выше правила. Чтобы развернуть эти правила из вашей локальной файловой системы (а не с помощью консоли Firebase), вы должны выполнить следующую команду:

firebase deploy --only firestore:rules

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

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

Чтобы узнать больше о Cloud Firestore, посетите следующие ресурсы: