Сборка с помощью 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
  • Установите 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 Marketplace или найти его в 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. Определите схему для обзоров фильмов.

В этом разделе вы определите структуру и связи между ключевыми сущностями приложения для просмотра фильмов в схеме. Такие сущности, как Movie , User , Actor и Review , сопоставляются с таблицами базы данных, а связи устанавливаются с помощью директив Firebase Data Connect и GraphQL. После этого ваше приложение будет готово к любым задачам: от поиска фильмов с самым высоким рейтингом и фильтрации по жанру до предоставления пользователям возможности оставлять отзывы, добавлять фильмы в избранное, просматривать похожие фильмы или находить рекомендуемые фильмы на основе введенного текста с помощью векторного поиска.

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

Тип 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
}

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

  • Movie! @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. Проверьте терминал Data Connect Execution, чтобы убедиться, что данные были успешно добавлены.
    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. Вы также будете использовать данные аутентификации Firebase для прямого извлечения и обновления данных пользователей в 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, а не пользователем или приложением, что добавляет дополнительный уровень безопасности, гарантируя безопасную и автоматическую обработку идентификатора пользователя.

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

  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_ fields: Эти поля представляют таблицы соединений:
    • 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, гарантируя, что доступ или изменение будут осуществляться только к данным аутентифицированного пользователя.

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

  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 для 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 для 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. Нажмите кнопку Запустить (локально) в 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.

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