Сборка с помощью Firebase Data Connect (веб-версия)

1. Прежде чем начать

Приложение FriendlyMovies

В этом практическом занятии вы интегрируете Firebase Data Connect с базой данных Cloud SQL для создания веб-приложения для обзоров фильмов. Готовое приложение демонстрирует, как Firebase Data Connect упрощает процесс создания приложений на основе SQL. Оно включает в себя следующие функции:

  • Аутентификация: Внедрите собственную аутентификацию для запросов и изменений вашего приложения, гарантируя, что только авторизованные пользователи смогут взаимодействовать с вашими данными.
  • Схема GraphQL: Создавайте и управляйте своими структурами данных, используя гибкую схему GraphQL, адаптированную под потребности веб-приложения для обзоров фильмов.
  • SQL-запросы и мутации: извлечение, обновление и управление данными в Cloud SQL с помощью запросов и мутаций на основе GraphQL.
  • Расширенный поиск с частичным совпадением строк: используйте фильтры и параметры поиска, чтобы находить фильмы по таким полям, как название, описание или теги.
  • (Необязательно) Интеграция векторного поиска: добавьте функцию поиска контента с помощью векторного поиска Firebase Data Connect, чтобы обеспечить удобный пользовательский интерфейс на основе введенных данных и предпочтений.

Предварительные требования

Вам потребуется базовое знание JavaScript .

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

  • Настройте Firebase Data Connect с использованием локальных эмуляторов.
  • Разработайте схему данных с использованием Data Connect и GraphQL .
  • Разрабатывайте и тестируйте различные запросы и мутации для приложения, предназначенного для написания отзывов о фильмах.
  • Узнайте, как Firebase Data Connect генерирует и использует SDK в приложении.
  • Разверните свою схему и эффективно управляйте базой данных.

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

  • Гит
  • Visual Studio Code
  • Установите Node.js с помощью nvm-windows (Windows) или nvm (macOS/Linux).
  • Если вы еще этого не сделали, создайте проект Firebase в консоли Firebase.
  • (Необязательно) Для векторного поиска перейдите на тарифный план Blaze с оплатой по факту использования.

2. Настройте среду разработки.

На этом этапе практического занятия вы настроите среду для начала разработки приложения для обзоров фильмов с использованием Firebase Data Connect.

  1. Клонируйте репозиторий проекта и установите необходимые зависимости:
    git clone https://github.com/firebaseextended/codelab-dataconnect-web
    cd codelab-dataconnect-web
    cd ./app && npm i
    npm run dev
    
  2. После выполнения этих команд откройте в браузере http://localhost:5173, чтобы увидеть работающее локально веб-приложение. Оно послужит вашим интерфейсом для создания приложения для обзоров фильмов и взаимодействия с его функциями. 93f6648a2532c606.png
  3. Откройте клонированную папку codelab-dataconnect-web с помощью Visual Studio Code . Здесь вы определите свою схему, напишете запросы и протестируете функциональность приложения.
  4. Для использования функций Data Connect установите расширение Firebase Data Connect для Visual Studio .
    В качестве альтернативы вы можете установить расширение из магазина расширений Visual Studio Code или найти его в самом VS Code. b03ee38c9a81b648.png
  5. Откройте или создайте новый проект Firebase в консоли Firebase .
  6. Подключите свой проект Firebase к расширению Firebase Data Connect для VSCode. В расширении выполните следующие действия:
    1. Нажмите кнопку «Войти» .
    2. Нажмите «Подключить проект Firebase» и выберите свой проект Firebase.
    4bb2fbf8f9fac29b.png
  7. Запустите эмуляторы Firebase с помощью расширения Firebase Data Connect для VS Code:
    Нажмите «Запустить эмуляторы» , а затем убедитесь, что эмуляторы запущены в терминале. 6d3d95f4cb708db1.png

3. Просмотрите исходный код.

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

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

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

Каталог dataconnect/

Содержит конфигурации Firebase Data Connect, коннекторы (определяющие запросы и мутации) и файлы схем.

  • schema/schema.gql : Определяет схему GraphQL.
  • connector/queries.gql : Запросы, необходимые в вашем приложении
  • connector/mutations.gql : Необходимые мутации в вашем приложении
  • connector/connector.yaml : Файл конфигурации для генерации SDK

Каталог app/src/

Содержит логику приложения и взаимодействие с Firebase Data Connect.

  • firebase.ts : Конфигурация для подключения к приложению Firebase в вашем проекте Firebase.
  • lib/dataconnect-sdk/ : Содержит сгенерированный SDK. Вы можете изменить местоположение генерации SDK в файле connector/connector.yaml , и SDK будут автоматически генерироваться каждый раз, когда вы определяете запрос или мутацию.

4. Разработайте схему для обзоров фильмов.

In this section, you'll define the structure and relationships between the key entities in the movie application in a schema. Entities such as Movie , User , Actor , and Review are mapped to database tables, with relationships established using Firebase Data Connect and GraphQL schema directives. Once it's in place, your app will be ready to handle everything from searching for top-rated movies and filtering by genre to letting users leave reviews, mark favorites, explore similar movies, or find recommended movies based on text input through vector search.

Основные сущности и взаимоотношения

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

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

Подготовьте стол для просмотра Movie .

Тип Movie определяет основную структуру объекта «Фильм», включая такие поля, как title , genre , releaseYear и rating .

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type Movie
  @table {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
}

Основные выводы:

  • id: Уникальный UUID для каждого фильма, сгенерированный с помощью @default(expr: "uuidV4()") .

Настройте таблицу MovieMetadata

Тип MovieMetadata устанавливает однозначное соответствие с типом Movie . Он включает дополнительные данные, такие как режиссер фильма.

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type MovieMetadata
  @table {
  # @ref creates a field in the current table (MovieMetadata)
  # It is a reference that holds the primary key of the referenced type
  # In this case, @ref(fields: "movieId", references: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String
}

Основные выводы:

  • Фильм! @ref: Ссылка на тип Movie , устанавливающая связь по внешнему ключу.

Настройте таблицу Actor .

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type Actor @table {
  id: UUID!
  imageUrl: String!
  name: String! @col(name: "name", dataType: "varchar(30)")
}

Тип Actor представляет собой актера в базе данных фильмов, причем каждый актер может участвовать в нескольких фильмах, образуя связь «многие ко многим».

Настройте таблицу MovieActor

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type MovieActor @table(key: ["movie", "actor"]) {
  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie!
  # movieId: UUID! <- this is created by the implied @ref, see: implicit.gql

  actor: Actor!
  # actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql

  role: String! # "main" or "supporting"
}

Основные выводы:

  • movie: Ссылается на тип Movie, неявно генерирует внешний ключ movieId: UUID!.
  • actor: Ссылается на тип Actor, неявно генерирует внешний ключ actorId: UUID!.
  • Роль: Определяет роль актера в фильме (например, «главный» или «второстепенный»).

Настройте таблицу User .

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

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type User
  @table {
  id: String! @col
  username: String! @col(dataType: "varchar(50)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user
  # movies_via_Review
}

Настройте таблицу FavoriteMovie .

Тип FavoriteMovie — это промежуточная таблица, которая обрабатывает связи «многие ко многим» между пользователями и их любимыми фильмами. Каждая таблица связывает User с Movie .

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type FavoriteMovie
  @table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
  # @ref is implicit
  user: User!
  movie: Movie!
}

Основные выводы:

  • movie: Ссылается на тип Movie, неявно генерирует внешний ключ movieId: UUID! .
  • user: Ссылается на тип пользователя, неявно генерирует внешний ключ. userId: UUID! .

Настройте таблицу Review .

Тип Review представляет собой сущность отзыва и связывает типы User и Movie в отношении «многие ко многим» (один пользователь может оставить много отзывов, и у каждого фильма может быть много отзывов).

Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql :

type Review @table(name: "Reviews", key: ["movie", "user"]) {
  id: UUID! @default(expr: "uuidV4()")
  user: User!
  movie: Movie!
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

Основные выводы:

  • Пользователь: Отсылает к пользователю, оставившем отзыв.
  • фильм: Отсылка к фильму, который рецензируется.
  • reviewDate: Автоматически устанавливается на время создания отзыва с помощью @default(expr: "request.time") .

Автоматически генерируемые поля и значения по умолчанию

Схема использует выражения типа @default(expr: "uuidV4()") для автоматической генерации уникальных идентификаторов и временных меток. Например, поле id в типах Movie и Review автоматически заполняется UUID при создании новой записи.

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

5. Найти лучшие и самые новые фильмы.

Приложение FriendlyMovies

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

Вставьте условные данные о фильме, актере и отзыве.

  1. В VSCode откройте dataconnect/moviedata_insert.gql . Убедитесь, что эмуляторы в расширении Firebase Data Connect запущены.
  2. В верхней части файла вы должны увидеть кнопку « Запустить (локально)» . Нажмите на неё, чтобы вставить данные фиктивного фильма в вашу базу данных.
    e424f75e63bf2e10.png
  3. Проверьте терминал выполнения подключения данных , чтобы убедиться в успешном добавлении данных.
    e0943d7704fb84ea.png

Реализуйте коннектор

  1. Откройте dataconnect/movie-connector/queries.gql . В комментариях вы найдете базовый запрос ListMovies :
    query ListMovies @auth(level: PUBLIC) {
      movies {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
    Этот запрос извлекает все фильмы и их подробную информацию (например, id , title , releaseYear ). Однако он не сортирует фильмы.
  2. Замените существующий запрос ListMovies следующим запросом, чтобы добавить параметры сортировки и ограничения:
    # List subset of fields for movies
    query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) {
      movies(
        orderBy: [
          { rating: $orderByRating },
          { releaseYear: $orderByReleaseYear }
        ]
        limit: $limit
      ) {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
  3. Нажмите кнопку « Выполнить (локально)» , чтобы выполнить запрос к вашей локальной базе данных. Вы также можете ввести переменные запроса в панели конфигурации перед запуском.
    c4d947115bb11b16.png

Основные выводы:

  • movies() : Поле запроса GraphQL для получения данных о фильмах из базы данных.
  • orderByRating : Параметр для сортировки фильмов по рейтингу (по возрастанию/убыванию).
  • orderByReleaseYear : Параметр для сортировки фильмов по году выпуска (по возрастанию/убыванию).
  • limit : Ограничивает количество возвращаемых фильмов.

Интегрируйте запросы в веб-приложение.

В этой части практического занятия вы будете использовать запросы, определенные в предыдущем разделе, в своем веб-приложении. Эмуляторы Firebase Data Connect генерируют SDK на основе информации из файлов .gql (в частности, schema.gql , queries.gql , mutations.gql ) и файла connector.yaml . Эти SDK можно напрямую вызывать в вашем приложении.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте оператор импорта вверху:
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    Функция listMovies , тип ответа ListMoviesData и перечисление OrderDirection — это SDK, сгенерированные эмуляторами Firebase Data Connect на основе схемы и запросов, которые вы определили ранее.
  2. Замените функции handleGetTopMovies и handleGetLatestMovies следующим кодом:
    // Fetch top-rated movies
    export const handleGetTopMovies = async (
      limit: number
    ): Promise<ListMoviesData["movies"] | null> => {
      try {
        const response = await listMovies({
          orderByRating: OrderDirection.DESC,
          limit,
        });
        return response.data.movies;
      } catch (error) {
        console.error("Error fetching top movies:", error);
        return null;
      }
    };
    
    // Fetch latest movies
    export const handleGetLatestMovies = async (
      limit: number
    ): Promise<ListMoviesData["movies"] | null> => {
      try {
        const response = await listMovies({
          orderByReleaseYear: OrderDirection.DESC,
          limit,
        });
        return response.data.movies;
      } catch (error) {
        console.error("Error fetching latest movies:", error);
        return null;
      }
    };
    

Основные выводы:

  • listMovies : Автоматически генерируемая функция, которая вызывает запрос listMovies для получения списка фильмов. Она включает параметры сортировки по рейтингу или году выпуска, а также ограничение количества результатов.
  • ListMoviesData : Тип результата, используемый для отображения 10 самых популярных и последних фильмов на главной странице приложения.

Посмотрите, как это работает.

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

6. Отображение информации о фильме и актере.

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

ac7fefa7ff779231.png

Внедрить коннекторы

  1. Откройте dataconnect/movie-connector/queries.gql в вашем проекте.
  2. Добавьте следующие запросы для получения информации о фильме и актере:
    # Get movie by id
    query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
    movie(id: $id) {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        description
        tags
        metadata: movieMetadatas_on_movie {
          director
        }
        mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
          id
          name
          imageUrl
        }
        supportingActors: actors_via_MovieActor(
          where: { role: { eq: "supporting" } }
        ) {
          id
          name
          imageUrl
        }
        reviews: reviews_on_movie {
          id
          reviewText
          reviewDate
          rating
          user {
            id
            username
          }
        }
      }
    }
    
    # Get actor by id
    query GetActorById($id: UUID!) @auth(level: PUBLIC) {
      actor(id: $id) {
        id
        name
        imageUrl
        mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) {
          id
          title
          genre
          tags
          imageUrl
        }
        supportingActors: movies_via_MovieActor(
          where: { role: { eq: "supporting" } }
        ) {
          id
          title
          genre
          tags
          imageUrl
        }
      }
    }
    
  3. Сохраните изменения и просмотрите запросы.

Основные выводы:

  • movie() / actor() : Поля запроса GraphQL для получения информации о конкретном фильме или актере из таблиц Movies или Actors .
  • _on_ : Это позволяет напрямую получать доступ к полям связанного типа, имеющего связь по внешнему ключу. Например, reviews_on_movie извлекает все отзывы, относящиеся к конкретному фильму.
  • _via_ : Используется для навигации по связям «многие ко многим» через таблицу связей. Например, actors_via_MovieActor обращается к типу Actor через таблицу связей MovieActor , а условие where фильтрует актеров на основе их роли (например, «главный» или «второстепенный»).

Проверьте запрос, введя фиктивные данные.

  1. В панели выполнения запроса Data Connect вы можете протестировать запрос, введя фиктивные идентификаторы, например:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Нажмите кнопку «Выполнить (локально)» для GetMovieById , чтобы получить подробную информацию о фильме «Квантовый парадокс» (фиктивный фильм, к которому относится указанный выше идентификатор).

1b08961891e44da2.png

Интегрируйте запросы в веб-приложение.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующие импорты:
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. Замените функции handleGetMovieById и handleGetActorById следующим кодом:
    // Fetch movie details by ID
    export const handleGetMovieById = async (
      movieId: string
    ) => {
      try {
        const response = await getMovieById({ id: movieId });
        if (response.data.movie) {
          return response.data.movie;
        }
        return null;
      } catch (error) {
        console.error("Error fetching movie:", error);
        return null;
      }
    };
    
    // Calling generated SDK for GetActorById
    export const handleGetActorById = async (
      actorId: string
    ): Promise<GetActorByIdData["actor"] | null> => {
      try {
        const response = await getActorById({ id: actorId });
        if (response.data.actor) {
          return response.data.actor;
        }
        return null;
      } catch (error) {
        console.error("Error fetching actor:", error);
        return null;
      }
    };
    

Основные выводы:

  • getMovieById / getActorById : Это автоматически сгенерированные функции, которые вызывают определенные вами запросы, извлекая подробную информацию о конкретном фильме или актере.
  • GetMovieByIdData / GetActorByIdData : Это типы результатов, используемые для отображения сведений о фильме и актере в приложении.

Посмотрите, как это работает.

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

7. Обработка аутентификации пользователей.

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

9890838045d5a00e.png

Внедрить коннекторы

  1. Откройте mutations.gql в dataconnect/movie-connector/ .
  2. Добавьте следующую мутацию для создания или обновления текущего аутентифицированного пользователя:
    # Create or update the current authenticated user
    mutation UpsertUser($username: String!) @auth(level: USER) {
      user_upsert(
        data: {
          id_expr: "auth.uid"
          username: $username
        }
      )
    }
    

Основные выводы:

  • id_expr: "auth.uid" : Здесь используется auth.uid , который предоставляется непосредственно Firebase Authentication, а не пользователем или приложением, что обеспечивает дополнительный уровень безопасности за счет безопасной и автоматической обработки идентификатора пользователя.

Получить текущего пользователя

  1. Откройте queries.gql в dataconnect/movie-connector/ .
  2. Добавьте следующий запрос для получения информации о текущем пользователе:
    # Get user by ID
    query GetCurrentUser @auth(level: USER) {
      user(key: { id_expr: "auth.uid" }) {
        id
        username
        reviews: reviews_on_user {
          id
          rating
          reviewDate
          reviewText
          movie {
            id
            title
          }
        }
        favoriteMovies: favorite_movies_on_user {
          movie {
            id
            title
            genre
            imageUrl
            releaseYear
            rating
            description
            tags
            metadata: movieMetadatas_on_movie {
              director
            }
          }
        }
      }
    }
    

Основные выводы:

  • auth.uid : Этот параметр извлекается непосредственно из Firebase Authentication, обеспечивая безопасный доступ к данным конкретного пользователя.
  • Поля _on_ : Эти поля представляют собой таблицы связей:
    • reviews_on_user : Получает все отзывы, относящиеся к данному пользователю, включая id и title фильма.
    • favorite_movies_on_user : Извлекает все фильмы, отмеченные пользователем как избранные, включая подробную информацию, такую ​​как genre , releaseYear , rating и metadata .

Интегрируйте запросы в веб-приложение.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующие импорты:
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. Замените функции handleAuthStateChange и handleGetCurrentUser следующим кодом:
    // Handle user authentication state changes and upsert user
    export const handleAuthStateChange = (
      auth: any,
      setUser: (user: User | null) => void
    ) => {
      return onAuthStateChanged(auth, async (user) => {
        if (user) {
          setUser(user);
          const username = user.email?.split("@")[0] || "anon";
          await upsertUser({ username });
        } else {
          setUser(null);
        }
      });
    };
    
    // Fetch current user profile
    export const handleGetCurrentUser = async (): Promise<
      GetCurrentUserData["user"] | null
    > => {
      try {
        const response = await getCurrentUser();
        return response.data.user;
      } catch (error) {
        console.error("Error fetching user profile:", error);
        return null;
      }
    };
    

Основные выводы:

  • handleAuthStateChange : Эта функция отслеживает изменения состояния аутентификации. Когда пользователь входит в систему, она устанавливает данные пользователя и вызывает мутацию upsertUser для создания или обновления информации о пользователе в базе данных.
  • handleGetCurrentUser : Получает профиль текущего пользователя с помощью запроса getCurrentUser , который извлекает отзывы пользователя и его любимые фильмы.

Посмотрите, как это работает.

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

8. Реализация взаимодействия с пользователем

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

b3d0ac1e181c9de9.png

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

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

Внедрить коннекторы

  1. Откройте mutations.gql в dataconnect/movie-connector/ .
  2. Добавьте следующие изменения для обработки добавления фильмов в избранное:
    # Add a movie to the user's favorites list
    mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
    }
    
    # Remove a movie from the user's favorites list
    mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
    }
    
    

Основные выводы:

  • userId_expr: "auth.uid" : Использует auth.uid , который предоставляется непосредственно Firebase Authentication, гарантируя, что доступ к данным или их изменение будут осуществляться только для аутентифицированного пользователя.

Проверьте, добавлен ли фильм в избранное.

  1. Откройте queries.gql в dataconnect/movie-connector/ .
  2. Добавьте следующий запрос, чтобы проверить, добавлен ли фильм в избранное:
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

Основные выводы:

  • auth.uid : Обеспечивает безопасный доступ к данным конкретного пользователя с помощью аутентификации Firebase.
  • favorite_movie : Проверяет таблицу favorite_movies , чтобы узнать, отмечен ли конкретный фильм текущим пользователем как избранный.

Интегрируйте запросы в веб-приложение.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующие импорты:
    import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
    
  2. Замените функции handleAddFavoritedMovie , handleDeleteFavoritedMovie и handleGetIfFavoritedMovie следующим кодом:
    // Add a movie to user's favorites
    export const handleAddFavoritedMovie = async (
      movieId: string
    ): Promise<void> => {
      try {
        await addFavoritedMovie({ movieId });
      } catch (error) {
        console.error("Error adding movie to favorites:", error);
        throw error;
      }
    };
    
    // Remove a movie from user's favorites
    export const handleDeleteFavoritedMovie = async (
      movieId: string
    ): Promise<void> => {
      try {
        await deleteFavoritedMovie({ movieId });
      } catch (error) {
        console.error("Error removing movie from favorites:", error);
        throw error;
      }
    };
    
    // Check if the movie is favorited by the user
    export const handleGetIfFavoritedMovie = async (
      movieId: string
    ): Promise<boolean> => {
      try {
        const response = await getIfFavoritedMovie({ movieId });
        return !!response.data.favorite_movie;
      } catch (error) {
        console.error("Error checking if movie is favorited:", error);
        return false;
      }
    };
    

Основные выводы:

  • handleAddFavoritedMovie и handleDeleteFavoritedMovie : Используйте эти мутации для безопасного добавления или удаления фильма из избранного пользователя.
  • handleGetIfFavoritedMovie : Использует запрос getIfFavoritedMovie для проверки того, отмечен ли фильм пользователем как избранный.

Посмотрите, как это работает.

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

Разрешите пользователям оставлять или удалять отзывы.

Далее вам предстоит реализовать в приложении раздел для управления отзывами пользователей.

Внедрить коннекторы

В mutations.gql ( dataconnect/movie-connector/mutations.gql ): добавьте следующие мутации:

# Add a review for a movie
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
@auth(level: USER) {
  review_insert(
    data: {
      userId_expr: "auth.uid"
      movieId: $movieId
      rating: $rating
      reviewText: $reviewText
      reviewDate_date: { today: true }
    }
  )
}

# Delete a user's review for a movie
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
  review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

Основные выводы:

  • userId_expr: "auth.uid" : Гарантирует, что отзывы связаны с аутентифицированным пользователем.
  • reviewDate_date: { today: true } : Автоматически генерирует текущую дату для обзора с помощью DataConnect, исключая необходимость ручного ввода.

Интегрируйте запросы в веб-приложение.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующие импорты:
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. Замените функции handleAddReview и handleDeleteReview следующим кодом:
    // Add a review to a movie
    export const handleAddReview = async (
      movieId: string,
      rating: number,
      reviewText: string
    ): Promise<void> => {
      try {
        await addReview({ movieId, rating, reviewText });
      } catch (error) {
        console.error("Error adding review:", error);
        throw error;
      }
    };
    
    // Delete a review from a movie
    export const handleDeleteReview = async (movieId: string): Promise<void> => {
      try {
        await deleteReview({ movieId });
      } catch (error) {
        console.error("Error deleting review:", error);
        throw error;
      }
    };
    

Основные выводы:

  • handleAddReview : Вызывает мутацию addReview для добавления отзыва к указанному фильму, обеспечивая безопасную привязку отзыва к авторизованному пользователю.
  • handleDeleteReview : Использует мутацию deleteReview для удаления отзыва о фильме, оставленного авторизованным пользователем.

Посмотрите, как это работает.

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

9. Расширенные фильтры и частичное сопоставление текста.

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

ece70ee0ab964e28.png

Внедрить коннекторы

  1. Откройте queries.gql в dataconnect/movie-connector/ .
  2. Добавьте следующий запрос для поддержки различных возможностей поиска:
    # Search for movies, actors, and reviews
    query SearchAll(
      $input: String
      $minYear: Int!
      $maxYear: Int!
      $minRating: Float!
      $maxRating: Float!
      $genre: String!
    ) @auth(level: PUBLIC) {
      moviesMatchingTitle: movies(
        where: {
          _and: [
            { releaseYear: { ge: $minYear } }
            { releaseYear: { le: $maxYear } }
            { rating: { ge: $minRating } }
            { rating: { le: $maxRating } }
            { genre: { contains: $genre } }
            { title: { contains: $input } }
          ]
        }
      ) {
        id
        title
        genre
        rating
        imageUrl
      }
      moviesMatchingDescription: movies(
        where: {
          _and: [
            { releaseYear: { ge: $minYear } }
            { releaseYear: { le: $maxYear } }
            { rating: { ge: $minRating } }
            { rating: { le: $maxRating } }
            { genre: { contains: $genre } }
            { description: { contains: $input } }
          ]
        }
      ) {
        id
        title
        genre
        rating
        imageUrl
      }
      actorsMatchingName: actors(where: { name: { contains: $input } }) {
        id
        name
        imageUrl
      }
      reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) {
        id
        rating
        reviewText
        reviewDate
        movie {
          id
          title
        }
        user {
          id
          username
        }
      }
    }
    

Основные выводы:

  • Оператор _and : объединяет несколько условий в одном запросе, позволяя фильтровать поиск по нескольким полям, таким как releaseYear , rating и genre .
  • Оператор contains : Ищет частичные текстовые совпадения внутри полей. В этом запросе он ищет совпадения в полях title , description , name или reviewText .
  • Условие where : Задает условия для фильтрации данных. В каждом разделе (фильмы, актеры, рецензии) используется условие where для определения конкретных критериев поиска.

Интегрируйте запросы в веб-приложение.

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующие импорты:
    import { searchAll, SearchAllData } from "@movie/dataconnect";
    
  2. Замените функцию handleSearchAll следующим кодом:
    // Function to perform the search using the query and filters
    export const handleSearchAll = async (
      searchQuery: string,
      minYear: number,
      maxYear: number,
      minRating: number,
      maxRating: number,
      genre: string
    ): Promise<SearchAllData | null> => {
      try {
        const response = await searchAll({
          input: searchQuery,
          minYear,
          maxYear,
          minRating,
          maxRating,
          genre,
        });
    
        return response.data;
      } catch (error) {
        console.error("Error performing search:", error);
        return null;
      }
    };
    

Основные выводы:

  • handleSearchAll : Эта функция использует запрос searchAll для выполнения поиска на основе ввода пользователя, фильтруя результаты по таким параметрам, как год, рейтинг, жанр и частичное совпадение текста.

Посмотрите, как это работает.

Перейдите на страницу «Расширенный поиск» в панели навигации веб-приложения. Теперь вы можете искать фильмы, актеров и рецензии, используя различные фильтры и поля ввода, получая подробные и персонализированные результаты поиска.

10. Дополнительно: Развертывание в облаке (требуется оплата)

Теперь, когда вы завершили локальную разработку, пришло время развернуть схему, данные и запросы на сервере. Это можно сделать с помощью расширения Firebase Data Connect для VS Code или Firebase CLI.

Обновите свой тарифный план Firebase.

Для интеграции Firebase Data Connect с Cloud SQL for PostgreSQL ваш проект Firebase должен использовать тарифный план с оплатой по мере использования (Blaze) , то есть быть привязан к учетной записи Cloud Billing .

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

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

Подключите ваше веб-приложение к вашему проекту Firebase.

  1. Зарегистрируйте свое веб-приложение в проекте Firebase с помощью консоли Firebase :
    1. Откройте свой проект и нажмите «Добавить приложение» .
    2. Пока что игнорируйте настройку и конфигурацию SDK, но обязательно скопируйте сгенерированный объект firebaseConfig .
    7030822793e4d75b.png
  2. Замените существующий firebaseConfig в app/src/lib/firebase.tsx конфигурацией, которую вы только что скопировали из консоли Firebase.
    const firebaseConfig = {
      apiKey: "API_KEY",
      authDomain: "PROJECT_ID.firebaseapp.com",
      projectId: "PROJECT_ID",
      storageBucket: "PROJECT_ID.firebasestorage.app",
      messagingSenderId: "SENDER_ID",
      appId: "APP_ID"
    };
    
  3. Создайте веб-приложение: Вернитесь в VS Code, в папку app , и используйте Vite для сборки веб-приложения для развертывания на хостинге:
    cd app
    npm run build
    

Настройте аутентификацию Firebase в вашем проекте Firebase.

  1. Настройте аутентификацию Firebase с использованием входа через Google. 62af2f225e790ef6.png
  2. (Необязательно) Разрешите домены для аутентификации Firebase с помощью консоли Firebase (например, http://127.0.0.1 ).
    1. В настройках аутентификации перейдите в раздел «Авторизованные домены» .
    2. Нажмите «Добавить домен» и добавьте свой локальный домен в список.

c255098f12549886.png

Развертывание с помощью Firebase CLI

  1. В dataconnect/dataconnect.yaml убедитесь, что идентификатор экземпляра, базы данных и службы соответствуют вашему проекту:
    specVersion: "v1alpha"
    serviceId: "your-service-id"
    location: "us-central1"
    schema:
      source: "./schema"
      datasource:
        postgresql:
          database: "your-database-id"
          cloudSql:
            instanceId: "your-instance-id"
    connectorDirs: ["./movie-connector"]
    
  2. Убедитесь, что в вашем проекте настроен Firebase CLI:
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
    
  3. Для развертывания выполните следующую команду в терминале:
    firebase deploy --only dataconnect,hosting
    
  4. Выполните эту команду, чтобы сравнить изменения в схеме:
    firebase dataconnect:sql:diff
    
  5. Если изменения приемлемы, примените их следующим образом:
    firebase dataconnect:sql:migrate
    

Ваш экземпляр Cloud SQL for PostgreSQL будет обновлен с использованием окончательно развернутой схемы и данных. Вы можете отслеживать статус в консоли Firebase.

Теперь вы сможете увидеть свое приложение в рабочем режиме по адресу your-project.web.app/ . Кроме того, вы можете нажать кнопку «Запустить (Производство)» на панели Firebase Data Connect, как и в случае с локальными эмуляторами, чтобы добавить данные в производственную среду.

11. Дополнительно: Векторный поиск с помощью Firebase Data Connect (требуется оплата)

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

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

4b5aca5a447d2feb.png

Обновите схему, добавив в нее векторные представления для поля.

В dataconnect/schema/schema.gql добавьте поле descriptionEmbedding в таблицу Movie :

type Movie
  # The below parameter values are generated by default with @table, and can be edited manually.
  @table {
  # implicitly calls @col to generates a column name. ex: @col(name: "movie_id")
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
  descriptionEmbedding: Vector @col(size:768) # Enables vector search
}

Основные выводы:

  • descriptionEmbedding: Vector @col(size:768) : Это поле хранит семантические векторные представления описаний фильмов, позволяя осуществлять векторный поиск контента в вашем приложении.

Активировать Vertex AI

  1. Для настройки API Vertex AI из Google Cloud следуйте инструкциям в руководстве по предварительным условиям . Этот шаг необходим для поддержки функций генерации встраиваний и векторного поиска.
  2. Для активации pgvector и векторного поиска повторно разверните свою схему, нажав кнопку «Развернуть в продакшене» с помощью расширения Firebase Data Connect для VS Code.

Заполните базу данных векторными представлениями.

  1. Откройте папку dataconnect в VS Code.
  2. Нажмите кнопку Run(local) в файле optional_vector_embed.gql , чтобы заполнить базу данных векторными представлениями фильмов.

b858da780f6ec103.png

Добавьте запрос векторного поиска

В dataconnect/movie-connector/queries.gql добавьте следующий запрос для выполнения векторного поиска:

# Search movie descriptions using L2 similarity with Vertex AI
query SearchMovieDescriptionUsingL2Similarity($query: String!)
@auth(level: PUBLIC) {
  movies_descriptionEmbedding_similarity(
    compare_embed: { model: "textembedding-gecko@003", text: $query }
    method: L2
    within: 2
    limit: 5
  ) {
    id
    title
    description
    tags
    rating
    imageUrl
  }
}

Основные выводы:

  • compare_embed : Указывает модель встраивания ( textembedding-gecko@003 ) и входной текст ( $query ) для сравнения.
  • method : Задает метод подобия ( L2 ), который представляет собой евклидово расстояние.
  • within : Ограничивает поиск фильмами с расстоянием L2 2 или меньше, фокусируясь на близких по содержанию совпадениях.
  • limit : Ограничивает количество возвращаемых результатов до 5.

Реализуйте функцию векторного поиска в вашем приложении.

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

  1. В app/src/lib/ MovieService.ts раскомментируйте следующие импорты из SDK; это будет работать как любой другой запрос.
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. Добавьте следующую функцию для интеграции векторного поиска в приложение:
    // Perform vector-based search for movies based on description
    export const searchMoviesByDescription = async (
      query: string
    ): Promise<
      | SearchMovieDescriptionUsingL2similarityData["movies_descriptionEmbedding_similarity"]
      | null
    > => {
      try {
        const response = await searchMovieDescriptionUsingL2similarity({ query });
        return response.data.movies_descriptionEmbedding_similarity;
      } catch (error) {
        console.error("Error fetching movie descriptions:", error);
        return null;
      }
    };
    

Основные выводы:

  • searchMoviesByDescription : Эта функция вызывает запрос searchMovieDescriptionUsingL2similarity , передавая входной текст для выполнения векторного поиска контента.

Посмотрите, как это работает.

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

7b71f1c75633c1be.png

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

Поздравляем, теперь вы можете использовать веб-приложение! Если вы хотите поэкспериментировать со своими собственными данными о фильмах, не беспокойтесь, вставляйте свои данные с помощью расширения Firebase Data Connect, имитируя файлы _insert.gql , или добавляйте их через панель выполнения Data Connect в VS Code.

Узнать больше