Создание с помощью Firebase Data Connect (веб),Создание с помощью Firebase Data Connect (веб),Создание с помощью 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/movie-connector/ mutations.gql
  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/movie-connector/ mutations.gql
  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.

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

,

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!.
  • Актер: Ссылка на тип актера, неявно генерирует акторид иностранного ключа: uuid!.
  • Роль: определяет роль актера в фильме (например, «Main» или «поддержка»).

Установите 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! .
  • Пользователь: ссылается на тип пользователя, неявно генерирует внешний ключ 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

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

Вставьте Mock Movie, актер и данные обзора

  1. В vscode откройте dataconnect/moviedata_insert.gql . Убедитесь, что эмуляторы в расширении данных Firebase Connect Connect.
  2. Вы должны увидеть кнопку запуска (локальный) в верхней части файла. Нажмите на это, чтобы вставить данные из фиктивных фильмов в свою базу данных.
    e424f75e63bf2e10.png
  3. Проверьте терминал выполнения Data Connect, чтобы подтвердить, что данные были успешно добавлены.
    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 : ограничивает количество возвращенных фильмов.

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

В этой части CodeLab вы будете использовать запросы, определенные в предыдущем разделе в вашем веб -приложении. Данные Firebase Data Connect Emulators генерируют 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 и Enum OrderDirection - все SDK, сгенерированные данными Firebase Data Connect Emulators на основе схемы и запросов, которые вы ранее определили.
  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 условия фильтры, основанные на их роли (например, «Main» или «поддержка»).

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

  1. На панели выполнения Data Connect вы можете протестировать запрос, введя макет, такие как:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Нажмите Run (Local) для 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 для непосредственного извлечения или Upsert пользовательских данных в Dataconnect Firebase, обеспечивая безопасное управление пользователями в вашем приложении.

9890838045D5A00E.PNG

Реализация разъемов

  1. Open 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. Open 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» в Navbar. Вы можете подписаться, используя эмулятор аутентификации Firebase. После входа нажмите «Мой профиль». На данный момент он будет пустым, но вы настроили основу для обработки данных для конкретного пользователя в вашем приложении.

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

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

b3d0ac1e181c9de9.png

Позвольте пользователю любить фильм

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

Реализация разъемов

  1. Open 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. Open 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. Open 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 для выполнения поиска на основе ввода пользователя, фильтрации результатов по таким параметрам, как год, рейтинг, жанр и частичный текст.

Увидеть это в действии

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

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

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

Обновите план ценообразования пожарной базы

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

Чтобы обновить свой проект до плана Blaze, выполните эти шаги:

  1. В консоли Firebase выберите, чтобы обновить свой план .
  2. Выберите план пламени. Следуйте инструкциям на экране, чтобы связать учетную запись облачного выставления счетов с вашим проектом.
    Если вам нужно было создать облачную биллинговую учетную запись в рамках этого обновления, вам может потребоваться перейти обратно к потоку обновления в консоли 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, в папке 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

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

  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. Убедитесь, что у вас есть CLI 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
    

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

Теперь вы должны увидеть ваше приложение в прямом эфире на your-project.web.app/ . Кроме того, вы можете щелкнуть запуск (производство) на панели подключения данных Firebase, как и с локальными эмуляторами, чтобы добавить данные в производственную среду.

11. Необязательно: векторный поиск с помощью данных Firebase Connect (требуется выставление счетов)

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

Этот шаг требует, чтобы вы выполнили последний шаг этого CodeLab для развертывания в 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) : В этом поле хранятся семантические встроенные встроенные введения описания фильмов, включающие поиск контента на основе вектора в вашем приложении.

Активировать вершину AI

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

Заполнить базу данных вторжениями

  1. Откройте папку dataconnect в коде VS.
  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.

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

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

  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 , передавая входной текст для выполнения поиска контента на основе вектора.

Увидеть это в действии

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

7b71f1c75633c1be.png

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

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

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

,

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

Приложение FriendlyMovies

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

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

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

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

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

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

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

  • Git
  • Visual Studio Code
  • Установить node.js с использованием NVM-Windows (Windows) или NVM (macos/linux)
  • Если вы еще этого не сделали, создайте проект Firebase в консоли Firebase
  • (Необязательно) Для векторного поиска обновите свой проект до плана платного ценообразования оплаты

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

На этом этапе CodeLab будет направлена ​​на создание среды, чтобы начать создавать приложение для обзора фильма, используя Data 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 . Здесь вы определите свою схему, напишите запросы и проверьте функциональность приложения.
  4. Чтобы использовать функции Data Connect, установите расширение данных Firebase Data Connect Visual Studio .
    В качестве альтернативы, вы можете установить расширение с рынка кодов Visual Studio или найти его в коде VS. b03ee38c9a81b648.png
  5. Откройте или создайте новый проект Firebase в консоли Firebase .
  6. Подключите свой проект Firebase к данным Firebase Подключить расширение VSCODE. В расширении сделайте следующее:
    1. Нажмите кнопку «Вин в» .
    2. Нажмите подключить проект Firebase и выберите свой проект Firebase.
    4BB2FBF8F9FAC29B.PNG
  7. Запустите эмуляторы Firebase, используя подключение данных Firebase Data VS Code:
    Нажмите «Начать эмуляторы» , а затем подтвердите, что эмуляторы работают в терминале. 6d3d95f4cb708db1.png

3. Просмотрите начальную кодовую базу

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

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

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

dataconnect/ Directory

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

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

app/src/ каталог

Содержит логику приложения и взаимодействие с Data Firebase 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!.
  • Актер: Ссылка на тип актера, неявно генерирует акторид иностранного ключа: uuid!.
  • Роль: определяет роль актера в фильме (например, «Main» или «поддержка»).

Установите 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! .
  • Пользователь: ссылается на тип пользователя, неявно генерирует внешний ключ 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

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

Вставьте Mock Movie, актер и данные обзора

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

Implement the connector

  1. Open dataconnect/movie-connector/queries.gql . You'll find a basic ListMovies query in the comments:
    query ListMovies @auth(level: PUBLIC) {
      movies {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
    This query fetches all movies and their details (for example, id , title , releaseYear ). However, it does not sort the movies.
  2. Replace the existing ListMovies query with the following query to add sorting and limit options:
    # 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. Click the Run (local) button to execute the query against your local database. You can also input the query variables in the configuration pane before running.
    c4d947115bb11b16.png

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

  • movies() : GraphQL query field for fetching movie data from the database.
  • orderByRating : Parameter to sort movies by rating (ascending/descending).
  • orderByReleaseYear : Parameter to sort movies by release year (ascending/descending).
  • limit : Restricts the number of movies returned.

Integrate queries in the web app

In this part of the codelab, you'll use the queries defined in the previous section in your web app. The Firebase Data Connect emulators generate SDKs based on the information in the .gql files (specifically, schema.gql , queries.gql , mutations.gql ) and the connector.yaml file. These SDKs can be directly called in your application.

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the import statement at the top:
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    The function listMovies , the response type ListMoviesData , and the enum OrderDirection are all SDKs generated by the Firebase Data Connect emulators based on the schema and the queries you've previously defined .
  2. Replace the handleGetTopMovies and handleGetLatestMovies functions with the following code:
    // 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 : An auto-generated function that calls the listMovies query to retrieve a list of movies. It includes options for sorting by rating or release year and limiting the number of results.
  • ListMoviesData : The result type used to display the top 10 and latest movies on the app's homepage.

See it in action

Reload your web app to see the query in action. The homepage now dynamically displays the list of movies, fetching data directly from your local database. You'll see the top-rated and latest movies appear seamlessly, reflecting the data you've just set up.

6. Display movie and actor details

In this section, you'll implement the functionality to retrieve detailed information for a movie or an actor using their unique IDs. This involves not only fetching data from their respective tables but also joining related tables to display comprehensive details, such as movie reviews and actor filmographies.

ac7fefa7ff779231.png

Implement connectors

  1. Open dataconnect/movie-connector/queries.gql in your project.
  2. Add the following queries to retrieve movie and actor details:
    # 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. Save your changes and review the queries.

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

  • movie() / actor() : GraphQL query fields for fetching a single movie or actor from the Movies or Actors table.
  • _on_ : This allows direct access to fields from an associated type that has a foreign key relationship. For example, reviews_on_movie fetches all reviews related to a specific movie.
  • _via_ : Used to navigate many-to-many relationships through a join table. For instance, actors_via_MovieActor accesses the Actor type through the MovieActor join table, and the where condition filters actors based on their role (for example, "main" or "supporting").

Test the query by inputting mock data

  1. In the Data Connect execution pane, you can test the query by inputting mock IDs, such as:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Click Run (local) for GetMovieById to retrieve the details about "Quantum Paradox" (the mock movie that the above ID relates to).

1b08961891e44da2.png

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. Replace the handleGetMovieById and handleGetActorById functions with the following code:
    // 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 : These are auto-generated functions that call the queries you defined, retrieving detailed information for a specific movie or actor.
  • GetMovieByIdData / GetActorByIdData : These are the result types, used to display movie and actor details in the app.

See it in action

Now, go to your web app's homepage. Click on a movie, and you'll be able to view all its details, including actors and reviews—information pulled from related tables. Similarly, clicking on an actor will display the movies they were part of.

7. Handle user authentication

In this section, you'll implement user sign-in and sign-out functionality using Firebase Authentication. You'll also use Firebase Authentication data to directly retrieve or upsert user data in Firebase DataConnect, ensuring secure user management within your app.

9890838045d5a00e.png

Implement connectors

  1. Open mutations.gql in dataconnect/movie-connector/ .
  2. Add the following mutation to create or update the current authenticated user:
    # 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" : This uses auth.uid , which is provided directly by Firebase Authentication, not by the user or the app, adding an extra layer of security by ensuring the user ID is handled securely and automatically.

Fetch the current user

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Add the following query to fetch the current user:
    # 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 : This is retrieved directly from Firebase Authentication, ensuring secure access to user-specific data.
  • _on_ fields: These fields represent the join tables:
    • reviews_on_user : Fetches all reviews related to the user, including the movie's id and title .
    • favorite_movies_on_user : Retrieves all movies marked as favorites by the user, including detailed information like genre , releaseYear , rating , and metadata .

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. Replace the handleAuthStateChange and handleGetCurrentUser functions with the following code:
    // 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 : This function listens for authentication state changes. When a user signs in, it sets the user's data and calls the upsertUser mutation to create or update the user's information in the database.
  • handleGetCurrentUser : Fetches the current user's profile using the getCurrentUser query, which retrieves the user's reviews and favorite movies.

See it in action

Now, click on the "Sign in with Google" button in the navbar. You can sign in using the Firebase Authentication emulator. After signing in, click on "My Profile". It will be empty for now, but you've set up the foundation for user-specific data handling in your app.

8. Implement user interactions

In this section of the codelab, you'll implement user interactions in the movie review app, specifically letting users manage their favorite movies and leave or delete reviews.

b3d0ac1e181c9de9.png

Let a user favorite a movie

In this section, you'll set up the database to let users favorite a movie.

Implement connectors

  1. Open mutations.gql in dataconnect/movie-connector/ .
  2. Add the following mutations to handle favoriting movies:
    # 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" : Uses auth.uid , which is provided directly by Firebase Authentication, ensuring that only the authenticated user's data is accessed or modified.

Check if a movie is favorited

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Add the following query to check if a movie is favorited:
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

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

  • auth.uid : Ensures secure access to user-specific data using Firebase Authentication.
  • favorite_movie : Checks the favorite_movies join table to see if a specific movie is marked as a favorite by the current user.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
    import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
    
  2. Replace the handleAddFavoritedMovie , handleDeleteFavoritedMovie , and handleGetIfFavoritedMovie functions with the following code:
    // 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 and handleDeleteFavoritedMovie : Use the mutations to add or remove a movie from the user's favorites securely.
  • handleGetIfFavoritedMovie : Uses the getIfFavoritedMovie query to check if a movie is marked as a favorite by the user.

See it in action

Now, you can favorite or unfavorite movies by clicking the heart icon on the movie cards and the movie details page. Additionally, you can view your favorite movies on your profile page.

Let users leave or delete reviews

Next, you'll implement the section for managing user reviews in the app.

Implement connectors

In mutations.gql ( dataconnect/movie-connector/mutations.gql ): Add the following mutations:

# 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" : Ensures that reviews are associated with the authenticated user.
  • reviewDate_date: { today: true } : Automatically generates the current date for the review using DataConnect, eliminating the need for manual input.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. Replace the handleAddReview and handleDeleteReview functions with the following code:
    // 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 : Calls the addReview mutation to add a review for the specified movie, securely linking it to the authenticated user.
  • handleDeleteReview : Uses the deleteReview mutation to remove a review for a movie by the authenticated user.

See it in action

Users can now leave reviews for movies on the movie details page. They can also view and delete their reviews on their profile page, giving them full control over their interactions with the app.

9. Advanced filters and partial text matching

In this section, you'll implement advanced search capabilities, allowing users to search movies based on a range of ratings and release years, filter by genres and tags, perform partial text matching in titles or descriptions, and even combine multiple filters for more precise results.

ece70ee0ab964e28.png

Implement connectors

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Add the following query to support various search capabilities:
    # 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 operator: Combines multiple conditions in a single query, allowing the search to be filtered by several fields like releaseYear , rating , and genre .
  • contains operator: Searches for partial text matches within fields. In this query, it looks for matches within title , description , name , or reviewText .
  • where clause: Specifies the conditions for filtering data. Each section (movies, actors, reviews) uses a where clause to define the specific criteria for the search.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
    import { searchAll, SearchAllData } from "@movie/dataconnect";
    
  2. Replace the handleSearchAll function with the following code:
    // 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 : This function uses the searchAll query to perform a search based on the user's input, filtering results by parameters like year, rating, genre, and partial text matches.

See it in action

Head over to the "Advanced Search" page from the navbar in the web app. You can now search for movies, actors, and reviews using various filters and inputs, getting detailed and tailored search results.

10. Optional: Deploy to Cloud (billing required)

Now that you've worked through the local development iteration, it's time to deploy your schema, data, and queries to the server. This can be done using the Firebase Data Connect VS Code extension or the Firebase CLI.

Upgrade your Firebase pricing plan

To integrate Firebase Data Connect with Cloud SQL for PostgreSQL, your Firebase project needs to be on the pay-as-you go (Blaze) pricing plan , which means it's linked to a Cloud Billing account .

  • A Cloud Billing account requires a payment method, like a credit card.
  • If you're new to Firebase and Google Cloud, check if you're eligible for a $300 credit and a Free Trial Cloud Billing account .
  • If you're doing this codelab as part of an event, ask your organizer if there are any Cloud credits available.

To upgrade your project to the Blaze plan, follow these steps:

  1. In the Firebase console, select to upgrade your plan .
  2. Select the Blaze plan. Follow the on-screen instructions to link a Cloud Billing account to your project.
    If you needed to create a Cloud Billing account as part of this upgrade, you might need to navigate back to the upgrade flow in the Firebase console to complete the upgrade.

Connect your web app to your Firebase project

  1. Register your web app in your Firebase project using the Firebase console :
    1. Open your project, and then click Add App .
    2. Ignore the SDK setup and configuration setup for now, but make sure to copy the generated firebaseConfig object.
    7030822793e4d75b.png
  2. Replace the existing firebaseConfig in app/src/lib/firebase.tsx with the configuration you just copied from the Firebase console.
    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. Build the web app: Back in VS Code, in the app folder, use Vite to build the web app for hosting deployment:
    cd app
    npm run build
    

Set up Firebase Authentication in your Firebase project

  1. Set up Firebase Authentication with Google Sign-In. 62af2f225e790ef6.png
  2. (Optional) Allow domains for Firebase Authentication using the Firebase console (for example, http://127.0.0.1 ).
    1. In the Authentication settings, go to Authorized Domains .
    2. Click "Add Domain" and include your local domain in the list.

c255098f12549886.png

Deploy with the Firebase CLI

  1. In dataconnect/dataconnect.yaml , ensure that your instance ID, database, and service ID match your project:
    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. Make sure that you have the Firebase CLI set up with your project:
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
    
  3. In your terminal, run the following command to deploy:
    firebase deploy --only dataconnect,hosting
    
  4. Run this command to compare your schema changes:
    firebase dataconnect:sql:diff
    
  5. If the changes are acceptable, apply them with:
    firebase dataconnect:sql:migrate
    

Your Cloud SQL for PostgreSQL instance will be updated with the final deployed schema and data. You can monitor the status in the Firebase console.

You should now be able to see your app live at your-project.web.app/ . Additionally, you can click Run (Production) in the Firebase Data Connect panel, just as you did with the local emulators, to add data to the production environment.

11. Optional: Vector search with Firebase Data Connect (billing required)

In this section, you'll enable vector search in your movie review app using Firebase Data Connect. This feature allows for content-based searches, such as finding movies with similar descriptions using vector embeddings.

This step requires that you completed the last step of this codelab to deploy to Google Cloud.

4b5aca5a447d2feb.png

Update the schema to include embeddings for a field

In dataconnect/schema/schema.gql , add the descriptionEmbedding field to the Movie table:

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) : This field stores the semantic embeddings of movie descriptions, enabling vector-based content search in your app.

Activate Vertex AI

  1. Follow the prerequisites guide to set up Vertex AI APIs from Google Cloud. This step is essential to support the embedding generation and vector search functionality.
  2. Re-deploy your schema to activate pgvector and vector search by clicking on "Deploy to Production" using the Firebase Data Connect VS Code extension.

Populate the database with embeddings

  1. Open the dataconnect folder in VS Code.
  2. Click Run(local) in optional_vector_embed.gql to populate your database with embeddings for the movies.

b858da780f6ec103.png

Add a vector search query

In dataconnect/movie-connector/queries.gql , add the following query to perform vector searches:

# 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 : Specifies the embedding model ( textembedding-gecko@003 ) and the input text ( $query ) for comparison.
  • method : Specifies the similarity method ( L2 ), which represents the Euclidean distance.
  • within : Limits the search to movies with an L2 distance of 2 or less, focusing on close content matches.
  • limit : Restricts the number of results returned to 5.

Implement the vector search function in your app

Now that the schema and query are set up, integrate the vector search into your app's service layer. This step allows you to call the search query from your web app.

  1. In app/src/lib/ MovieService.ts , uncomment the following imports from the SDKs, this will work like any other query.
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. Add the following function to integrate vector-based search into the app:
    // 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 : This function calls the searchMovieDescriptionUsingL2similarity query, passing the input text to perform a vector-based content search.

See it in action

Navigate to the "Vector Search" section in the navbar and type in phrases like "romantic and modern". You'll see a list of movies that match the content you're searching for, or, go into the movie details page of any movie, and check out the similar movies section at the bottom of the page.

7b71f1c75633c1be.png

12. Conclusion

Congratulations, you should be able to use the web app! If you want to play with your own movie data, don't worry, insert your own data using the Firebase Data Connect extension by mimicking the _insert.gql files, or add them through the Data Connect execution pane in VS Code.

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