Сборка с помощью 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, установите расширение Visual Studio Firebase Data Connect .
    Кроме того, вы можете установить расширение из 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
}

Ключевые выводы:

  • Фильм! @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"
}

Ключевые выводы:

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

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

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

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

type User
  @table {
  id: String! @col(name: "auth_uid")
  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!
}

Ключевые выводы:

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

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

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

Чтобы обновить проект до плана 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, как и в случае с локальными эмуляторами, чтобы добавить данные в производственную среду.

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.

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