Сборка с помощью 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.

Настройка среды разработки

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

Шаг 1. Клонируйте репозиторий проекта

Начните с клонирования репозитория проекта и установки необходимых зависимостей:

git clone https://github.com/firebaseextended/codelab-dataconnect-web
cd codelab-dataconnect-web
cd ./app && npm i
npm run dev
  1. После выполнения этих команд откройте http://localhost:5173 в браузере, чтобы увидеть, как веб-приложение работает локально. Это служит интерфейсом для создания приложения для просмотра фильмов и взаимодействия с его функциями.

93f6648a2532c606.png

Шаг 2. Откройте проект в коде Visual Studio.

Откройте клонированную папку codelab-dataconnect-web с помощью Visual Studio Code . Здесь вы определите свою схему, напишите запросы и протестируете функциональность приложения.

Шаг 3. Установите расширение Firebase Data Connect для Visual Studio

Чтобы использовать функции Data Connect, установите расширение Firebase Data Connect Visual Studio . Или: установите его из Visual Studio Code Marketplace или найдите его в VS Code.

  1. Или: установите его из Visual Studio Code Marketplace или найдите его в VS Code.

b03ee38c9a81b648.png

Шаг 4. Создайте проект Firebase

Перейдите в консоль Firebase , чтобы создать новый проект Firebase, если у вас его еще нет. Затем в расширении Firebase Data Connect VSCode:

  • Нажмите кнопку «Войти» .
  • Нажмите «Подключить проект Firebase» и выберите проект, созданный вами в консоли Firebase.

4bb2fbf8f9fac29b.png

Шаг 5. Запустите эмуляторы Firebase

В расширении Firebase Data Connect VSCode нажмите «Запустить эмуляторы» и убедитесь, что эмуляторы работают в терминале.

6d3d95f4cb708db1.png

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

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

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

Вот краткий обзор структуры папок и файлов приложения:

подключение к данным/

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

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

приложение/источник/

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

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

3. Определение схемы для обзора фильмов

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

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

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

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

Таблица фильмов

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

Скопируйте и вставьте фрагмент кода в файл 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 устанавливает связь «один к одному» с типом 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 , устанавливая связь по внешнему ключу.

Таблица актеров

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

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

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

Стол КиноАктера

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

Скопируйте и вставьте фрагмент кода в файл 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 — это таблица соединений, которая обрабатывает отношения «многие ко многим» между пользователями и их любимыми фильмами или актерами. Каждая таблица связывает 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!.

Обзорная таблица

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

Скопируйте и вставьте фрагмент кода в файл 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 в типах «Фильм» и «Обзор» автоматически заполняется UUID при создании новой записи.

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

4. Получение лучших и последних фильмов

Приложение FriendlyMovies

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

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

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

e424f75e63bf2e10.png

  1. Проверьте терминал 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
  }
}

Этот запрос извлекает все фильмы и их сведения (например, идентификатор, название, год выпуска). Однако он не сортирует фильмы.

  1. Замените запрос 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
  }
}

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

c4d947115bb11b16.png

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

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

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

В этой части вы будете использовать запросы, определенные в предыдущем разделе, в своем веб-приложении. Эмуляторы Firebase Data Connect генерируют SDK на основе информации в файлах .gql (schema.gql, query.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 на основе схемы и ранее определенных вами запросов.

  1. Замените функции 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 лучших и последних фильмов на главной странице.

Посмотрите это в действии

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

5. Отображение сведений о фильме и актере

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

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
    }
  }
}
  1. Сохраните изменения и просмотрите запросы.

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

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

На панели выполнения Data Connect вы можете протестировать запрос, введя фиктивные идентификаторы, например:

{"id": "550e8400-e29b-41d4-a716-446655440000"}

Нажмите «Выполнить» (локально) для GetMovieById чтобы получить подробную информацию о «Квантовом парадоксе» (фиктивном фильме, к которому относится указанный выше идентификатор).

1b08961891e44da2.png

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

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующий импорт:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. Замените функции 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 : это типы результатов, используемые для отображения сведений о фильме и актере в приложении.

Посмотрите это в действии

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

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

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

9890838045d5a00e.png

Реализация соединителей

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

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

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

Затем откройте queries.gql в dataconnect/movie-connector/ .

Добавьте следующий запрос для получения текущего пользователя:

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

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

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующий импорт:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. Замените функции 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 Auth. После входа нажмите «Мой профиль». На данный момент оно будет пустым, но вы заложили основу для обработки пользовательских данных в своем приложении.

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

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

b3d0ac1e181c9de9.png

Реализация соединителей

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

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

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

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

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

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

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

  1. В MovieService ( app/src/lib/MovieService.tsx ) раскомментируйте следующий импорт:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. Замените функции 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 , чтобы проверить, помечен ли фильм пользователем как избранный.

Посмотрите это в действии

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

Реализация отзывов пользователей

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

Реализация соединителей

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

Посмотрите это в действии

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

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

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

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

Посмотрите это в действии

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

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

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

Добавление веб-приложения в консоль Firebase

  1. Создайте веб-приложение в консоли Firebase и запишите свой идентификатор приложения.

7030822793e4d75b.png

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

Настройте аутентификацию Firebase в консоли

  1. Настройте Firebase Auth с помощью входа в Google

62af2f225e790ef6.png

  1. (Необязательно) Разрешите домены для (Firebase Auth) [https://firebase.google.com/docs/auth/web/hosting] в консоли вашего проекта (например, http://127.0.0.1 ):
  • В настройках аутентификации выберите свой проект и перейдите в раздел (Авторизованные домены) [https://firebase.google.com/docs/auth/web/hosting] . Нажмите «Добавить домен» и добавьте свой локальный домен в список.

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"]
  1. Убедитесь, что для вашего проекта настроен Firebase CLI.
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. В терминале выполните следующую команду для развертывания:
firebase deploy --only dataconnect,hosting
  1. Запустите эту команду, чтобы сравнить изменения схемы:
firebase dataconnect:sql:diff
  1. Если изменения приемлемы, примените их с помощью:
firebase dataconnect:sql:migrate

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

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

10. Необязательно: векторный поиск с помощью Firebase Data Connect.

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

4b5aca5a447d2feb.png

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

  1. В 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 VSCode.

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

  1. Откройте папку dataconnect в VSCode и нажмите «Выполнить (локально)» в optional_vector_embed.gql чтобы заполнить базу данных вставками для фильмов.

b858da780f6ec103.png

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

  1. В 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 раскомментируйте следующий импорт:

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

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

В app/src/lib/ MovieService.ts раскомментируйте следующие импортированные данные из SDK, это будет работать как любой другой запрос.

import {
  searchMovieDescriptionUsingL2similarity,
  SearchMovieDescriptionUsingL2similarityData,
} from "@movie/dataconnect";

Добавьте следующую функцию для интеграции векторного поиска в приложение:

// 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

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

Поздравляем, вы сможете использовать веб-приложение! Если вы хотите работать с собственными данными фильма, не волнуйтесь, вставьте свои собственные данные, используя расширение FDC, имитируя файлы _insert.gql, или добавьте их через панель «Выполнение подключения к данным».

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