1. Прежде чем начать
В этой лабораторной работе вы интегрируете 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.
- Клонируйте репозиторий проекта и установите необходимые зависимости:
git clone https://github.com/firebaseextended/codelab-dataconnect-web cd codelab-dataconnect-web cd ./app && npm i npm run dev
- После выполнения этих команд откройте в браузере http://localhost:5173, чтобы увидеть запущенное локально веб-приложение. Это интерфейс для создания приложения для просмотра фильмов и взаимодействия с его функциями.
- Откройте клонированную папку
codelab-dataconnect-web
с помощью Visual Studio Code . Здесь вы определите схему, напишете запросы и протестируете функциональность приложения. - Чтобы использовать функции Data Connect, установите расширение Firebase Data Connect Visual Studio .
Кроме того, вы можете установить расширение из Visual Studio Code Marketplace или найти его в VS Code. - Откройте или создайте новый проект Firebase в консоли Firebase .
- Подключите свой проект Firebase к расширению Firebase Data Connect VSCode. В расширении выполните следующие действия:
- Нажмите кнопку «Войти» .
- Нажмите Подключить проект Firebase и выберите свой проект Firebase.
- Запустите эмуляторы Firebase с помощью расширения Firebase Data Connect VS Code:
Нажмите «Запустить эмуляторы» , а затем подтвердите, что эмуляторы запущены в терминале.
3. Проверьте начальную кодовую базу.
В этом разделе вы изучите ключевые области исходного кода приложения. Хотя приложению не хватает некоторых функций, полезно понимать его общую структуру.
Структура папок и файлов
В следующих подразделах представлен обзор структуры папок и файлов приложения.
Каталог dataconnect/
Содержит конфигурации Firebase Data Connect, коннекторы (которые определяют запросы и мутации) и файлы схем.
-
schema/schema.gql
: определяет схему GraphQL -
connector/queries.gql
: Запросы, необходимые в вашем приложении -
connector/mutations.gql
: мутации, необходимые в вашем приложении -
connector/connector.yaml
: Файл конфигурации для генерации SDK
Каталог app/src/
Содержит логику приложения и взаимодействие с Firebase Data Connect.
-
firebase.ts
: конфигурация для подключения к приложению Firebase в вашем проекте Firebase. -
lib/dataconnect-sdk/
: содержит сгенерированный SDK. Вы можете изменить место генерации SDK в файлеconnector/connector.yaml
и SDK будут автоматически генерироваться при каждом определении запроса или мутации.
4. Определите схему для обзоров фильмов.
В этом разделе вы определите структуру и связи между ключевыми сущностями приложения для просмотра фильмов в схеме. Такие сущности, как Movie
, User
, Actor
и Review
, сопоставляются с таблицами базы данных, а связи устанавливаются с помощью директив Firebase Data Connect и GraphQL. После этого ваше приложение будет готово к любым задачам: от поиска фильмов с самым высоким рейтингом и фильтрации по жанру до предоставления пользователям возможности оставлять отзывы, добавлять фильмы в избранное, просматривать похожие фильмы или находить рекомендуемые фильмы на основе введенного текста с помощью векторного поиска.
Основные сущности и отношения
Тип Movie
содержит ключевые данные, такие как название, жанр и теги, которые приложение использует для поиска и создания профилей фильмов. Тип User
отслеживает действия пользователей, например, отзывы и добавление в избранное. Reviews
связывают пользователей с фильмами, позволяя приложению показывать оценки и отзывы пользователей.
Связи между фильмами, актёрами и пользователями делают приложение более динамичным. Таблица связей MovieActor
помогает отображать информацию об актёрском составе и фильмографии актёров. Тип FavoriteMovie
позволяет пользователям добавлять фильмы в избранное, поэтому приложение может отображать персонализированный список избранного и выделять популярные фильмы.
Настройте стол для просмотра Movie
Тип Movie
определяет основную структуру объекта «Фильм», включая такие поля, как title
, genre
, releaseYear
и rating
.
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type Movie
@table {
id: UUID! @default(expr: "uuidV4()")
title: String!
imageUrl: String!
releaseYear: Int
genre: String
rating: Float
description: String
tags: [String]
}
Основные выводы:
- id: уникальный UUID для каждого фильма, сгенерированный с помощью
@default(expr: "uuidV4()")
.
Настройте таблицу MovieMetadata
Тип MovieMetadata
устанавливает связь «один к одному» с типом Movie
. Он включает дополнительные данные, например, данные о режиссёре фильма.
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type MovieMetadata
@table {
# @ref creates a field in the current table (MovieMetadata)
# It is a reference that holds the primary key of the referenced type
# In this case, @ref(fields: "movieId", references: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String
}
Основные выводы:
- Movie! @ref: Ссылается на тип
Movie
, устанавливая связь по внешнему ключу.
Настройте таблицу Actor
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type Actor @table {
id: UUID!
imageUrl: String!
name: String! @col(name: "name", dataType: "varchar(30)")
}
Тип Actor
представляет собой актёра в базе данных фильмов, где каждый актёр может быть участником нескольких фильмов, образуя связь «многие ко многим».
Настройте таблицу MovieActor
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type MovieActor @table(key: ["movie", "actor"]) {
# @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie!
# movieId: UUID! <- this is created by the implied @ref, see: implicit.gql
actor: Actor!
# actorId: UUID! <- this is created by the implied @ref, see: implicit.gql
role: String! # "main" or "supporting"
}
Основные выводы:
- movie: Ссылается на тип Movie, неявно генерирует внешний ключ movieId: UUID!.
- Actor: Ссылается на тип Actor, неявно генерирует внешний ключ actorId: UUID!.
- Роль: Определяет роль актера в фильме (например, «главная» или «второстепенная»).
Настройте таблицу User
Тип User
определяет сущность пользователя, который взаимодействует с фильмами, оставляя рецензии или добавляя фильмы в избранное.
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type User
@table {
id: String! @col
username: String! @col(dataType: "varchar(50)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
Настройте таблицу FavoriteMovie
Тип FavoriteMovie
— это таблица, которая поддерживает связи типа «многие ко многим» между пользователями и их любимыми фильмами. Каждая таблица связывает User
с Movie
.
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type FavoriteMovie
@table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
# @ref is implicit
user: User!
movie: Movie!
}
Основные выводы:
- movie: Ссылается на тип Movie, неявно генерирует внешний ключ
movieId: UUID!
. - user: Ссылается на тип пользователя, неявно генерирует внешний ключ
userId: UUID!
.
Настройте таблицу Review
Тип Review
представляет собой сущность обзора и связывает типы « User
и Movie
отношением «многие ко многим» (один пользователь может оставить много обзоров, и каждый фильм может иметь много обзоров).
Скопируйте и вставьте фрагмент кода в файл dataconnect/schema/schema.gql
:
type Review @table(name: "Reviews", key: ["movie", "user"]) {
id: UUID! @default(expr: "uuidV4()")
user: User!
movie: Movie!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Основные выводы:
- пользователь: Ссылается на пользователя, оставившего отзыв.
- фильм: Ссылка на рецензируемый фильм.
- reviewDate: автоматически устанавливается на время создания обзора с помощью
@default(expr: "request.time")
.
Автоматически сгенерированные поля и значения по умолчанию
Схема использует выражения вроде @default(expr: "uuidV4()")
для автоматической генерации уникальных идентификаторов и временных меток. Например, поле id
в типах Movie
и Review
автоматически заполняется UUID при создании новой записи.
Теперь, когда схема определена, у вашего приложения для просмотра фильмов есть прочная основа для структуры данных и взаимосвязей!
5. Получить лучшие и последние фильмы
В этом разделе вы добавите данные о фиктивных фильмах в локальные эмуляторы, а затем реализуете коннекторы (запросы) и код TypeScript для вызова этих коннекторов в веб-приложении. В результате ваше приложение сможет динамически извлекать и отображать самые популярные и новые фильмы непосредственно из базы данных.
Вставьте данные о фиктивном фильме, актере и обзоре
- В VSCode откройте
dataconnect/moviedata_insert.gql
. Убедитесь, что эмуляторы в расширении Firebase Data Connect запущены. - В верхней части файла должна появиться кнопка «Запустить (локально)» . Нажмите её, чтобы вставить данные макета фильма в базу данных.
- Проверьте терминал Data Connect Execution, чтобы убедиться, что данные были успешно добавлены.
Реализовать соединитель
- Откройте
dataconnect/movie-connector/queries.gql
. В комментариях вы найдёте базовый запросListMovies
: Этот запрос выбирает все фильмы и их данные (например,query ListMovies @auth(level: PUBLIC) { movies { id title imageUrl releaseYear genre rating tags description } }
id
,title
,releaseYear
). Однако он не сортирует фильмы. - Замените существующий запрос
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 } }
- Нажмите кнопку «Выполнить (локально)» , чтобы выполнить запрос к локальной базе данных. Вы также можете ввести переменные запроса на панели конфигурации перед запуском.
Основные выводы:
-
movies()
: Поле запроса GraphQL для извлечения данных о фильмах из базы данных. -
orderByRating
: Параметр для сортировки фильмов по рейтингу (по возрастанию/по убыванию). -
orderByReleaseYear
: Параметр для сортировки фильмов по году выпуска (по возрастанию/по убыванию). -
limit
: Ограничивает количество возвращаемых фильмов.
Интеграция запросов в веб-приложение
В этой части лабораторной работы вы будете использовать запросы, определённые в предыдущем разделе, в своём веб-приложении. Эмуляторы Firebase Data Connect генерируют SDK на основе информации из файлов .gql
(в частности, schema.gql
, queries.gql
, mutations.gql
) и файла connector.yaml
. Эти SDK можно вызывать напрямую из вашего приложения.
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте оператор импорта вверху: Функцияimport { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
listMovies
, тип ответаListMoviesData
и перечислениеOrderDirection
— это SDK, сгенерированные эмуляторами Firebase Data Connect на основе схемы и запросов, которые вы ранее определили. - Замените функции
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. Отображение информации о фильме и актере
В этом разделе вы реализуете функционал для получения подробной информации о фильме или актёре по их уникальным идентификаторам. Это включает в себя не только извлечение данных из соответствующих таблиц, но и объединение связанных таблиц для отображения подробных данных, таких как обзоры фильмов и фильмографии актёров.
Реализовать соединители
- Откройте
dataconnect/movie-connector/queries.gql
в вашем проекте. - Добавьте следующие запросы для получения информации о фильмах и актерах:
# 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 } } }
- Сохраните изменения и просмотрите запросы.
Основные выводы:
-
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
, чтобы получить сведения о «Квантовом парадоксе» (фиктивном фильме, к которому относится указанный выше идентификатор).
Интеграция запросов в веб-приложение
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте следующие импорты:import { getMovieById, GetMovieByIdData } from "@movie/dataconnect"; import { GetActorByIdData, getActorById } from "@movie/dataconnect";
- Замените функции
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, обеспечивая безопасное управление пользователями в вашем приложении.
Реализовать соединители
- Откройте
mutations.gql
вdataconnect/movie-connector/
. - Добавьте следующую мутацию для создания или обновления текущего аутентифицированного пользователя:
# 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 Authentication, обеспечивая безопасный доступ к данным пользователя. -
_on_
fields: Эти поля представляют таблицы соединений:-
reviews_on_user
: Извлекает все обзоры, относящиеся к пользователю, включаяid
иtitle
фильма. -
favorite_movies_on_user
: Извлекает все фильмы, отмеченные пользователем как избранные, включая подробную информацию, такую какgenre
,releaseYear
,rating
иmetadata
.
-
Интеграция запросов в веб-приложение
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте следующие импорты:import { upsertUser } from "@movie/dataconnect"; import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
- Замените функции
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. Реализуйте взаимодействие с пользователем
В этом разделе лабораторной работы вы реализуете взаимодействие с пользователем в приложении для обзора фильмов, в частности, позволите пользователям управлять своими любимыми фильмами и оставлять или удалять обзоры.
Позвольте пользователю добавить фильм в избранное
В этом разделе вы настроите базу данных, чтобы пользователи могли добавлять фильмы в избранное.
Реализовать соединители
- Откройте
mutations.gql
вdataconnect/movie-connector/
. - Добавьте следующие мутации для обработки избранных фильмов:
# 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, гарантируя, что доступ или изменение будут осуществляться только к данным аутентифицированного пользователя.
Проверьте, добавлен ли фильм в избранное
- Откройте
queries.gql
вdataconnect/movie-connector/
. - Добавьте следующий запрос, чтобы проверить, добавлен ли фильм в избранное:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) { movieId } }
Основные выводы:
-
auth.uid
: обеспечивает безопасный доступ к данным пользователя с помощью аутентификации Firebase. -
favorite_movie
: проверяет таблицу соединенийfavorite_movies
, чтобы узнать, отмечен ли конкретный фильм как избранный текущим пользователем.
Интеграция запросов в веб-приложение
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте следующие импорты:import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
- Замените функции
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, устраняя необходимость ручного ввода.
Интеграция запросов в веб-приложение
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте следующие импорты:import { addReview, deleteReview } from "@movie/dataconnect";
- Замените функции
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. Расширенные фильтры и частичное сопоставление текста
В этом разделе вы реализуете расширенные возможности поиска, позволяющие пользователям искать фильмы по диапазону рейтингов и годов выпуска, фильтровать по жанрам и тегам, выполнять частичное сопоставление текста в названиях или описаниях и даже объединять несколько фильтров для получения более точных результатов.
Реализовать соединители
- Откройте
queries.gql
вdataconnect/movie-connector/
. - Добавьте следующий запрос для поддержки различных возможностей поиска:
# 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
для определения конкретных критериев поиска.
Интеграция запросов в веб-приложение
- В
MovieService
(app/src/lib/MovieService.tsx
) раскомментируйте следующие импорты:import { searchAll, SearchAllData } from "@movie/dataconnect";
- Замените функцию
handleSearchAll
следующим кодом:// Function to perform the search using the query and filters export const handleSearchAll = async ( searchQuery: string, minYear: number, maxYear: number, minRating: number, maxRating: number, genre: string ): Promise<SearchAllData | null> => { try { const response = await searchAll({ input: searchQuery, minYear, maxYear, minRating, maxRating, genre, }); return response.data; } catch (error) { console.error("Error performing search:", error); return null; } };
Основные выводы:
-
handleSearchAll
: эта функция использует запросsearchAll
для выполнения поиска на основе введенных пользователем данных, фильтруя результаты по таким параметрам, как год, рейтинг, жанр и частичное совпадение текста.
Посмотрите на это в действии
Перейдите на страницу «Расширенный поиск» с панели навигации веб-приложения. Теперь вы можете искать фильмы, актёров и обзоры, используя различные фильтры и параметры, получая подробные и персонализированные результаты поиска.
10. Дополнительно: развертывание в облаке (требуется оплата)
Теперь, когда вы завершили локальную итерацию разработки, пришло время развернуть схему, данные и запросы на сервере. Это можно сделать с помощью расширения Firebase Data Connect VS Code или Firebase CLI.
Обновите свой тарифный план Firebase
Для интеграции Firebase Data Connect с Cloud SQL для PostgreSQL ваш проект Firebase должен быть на тарифном плане с оплатой по мере использования (Blaze) , что означает, что он должен быть связан с учетной записью Cloud Billing .
- Для учетной записи Cloud Billing требуется способ оплаты, например кредитная карта.
- Если вы новичок в Firebase и Google Cloud, проверьте, имеете ли вы право на кредит в размере 300 долларов США и бесплатную пробную учетную запись Cloud Billing .
- Если вы выполняете эту практическую работу в рамках мероприятия, узнайте у организатора, доступны ли какие-либо облачные кредиты.
Чтобы обновить свой проект до плана Blaze, выполните следующие действия:
- В консоли Firebase выберите обновление вашего плана .
- Выберите тарифный план Blaze. Следуйте инструкциям на экране, чтобы подключить аккаунт Cloud Billing к своему проекту.
Если вам потребовалось создать учетную запись Cloud Billing в рамках этого обновления, вам может потребоваться вернуться к процессу обновления в консоли Firebase, чтобы завершить обновление.
Подключите ваше веб-приложение к вашему проекту Firebase
- Зарегистрируйте свое веб-приложение в проекте Firebase с помощью консоли Firebase :
- Откройте свой проект и нажмите «Добавить приложение» .
- Пока проигнорируйте настройку и конфигурирование SDK, но обязательно скопируйте сгенерированный объект
firebaseConfig
.
- Замените существующий
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" };
- Создайте веб-приложение: Вернитесь в VS Code, в папке
app
, используйте Vite для создания веб-приложения для развертывания хостинга:cd app npm run build
Настройте аутентификацию Firebase в своем проекте Firebase
- Настройте аутентификацию Firebase с помощью входа в Google.
- (Необязательно) Разрешите доменам аутентификацию Firebase с помощью консоли Firebase (например,
http://127.0.0.1
).- В настройках аутентификации перейдите в раздел Авторизованные домены .
- Нажмите «Добавить домен» и включите свой локальный домен в список.
Развертывание с помощью Firebase CLI
- В
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"]
- Убедитесь, что в вашем проекте настроен Firebase CLI:
npm i -g firebase-tools firebase login --reauth firebase use --add
- Для развертывания выполните в терминале следующую команду:
firebase deploy --only dataconnect,hosting
- Выполните эту команду, чтобы сравнить изменения схемы:
firebase dataconnect:sql:diff
- Если изменения приемлемы, примените их с помощью:
firebase dataconnect:sql:migrate
Ваш экземпляр Cloud SQL для PostgreSQL будет обновлён с учётом окончательной развёрнутой схемы и данных. Вы можете отслеживать статус в консоли Firebase.
Теперь вы можете увидеть своё приложение в режиме реального времени по адресу your-project.web.app/
. Кроме того, вы можете нажать кнопку «Запустить (Производство)» на панели Firebase Data Connect, как и в случае с локальными эмуляторами, чтобы добавить данные в производственную среду.
11. Дополнительно: поиск векторов с помощью Firebase Data Connect (требуется оплата)
В этом разделе вы включите поиск векторных изображений в своём приложении для обзора фильмов с помощью Firebase Data Connect. Эта функция позволяет осуществлять поиск по содержанию, например, находить фильмы с похожими описаниями, используя векторные вставки.
На этом этапе необходимо завершить последний шаг данной лабораторной работы по развертыванию в Google Cloud.
Обновите схему, включив в нее встраивание для поля.
В 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
- Следуйте руководству по настройке API Vertex AI из Google Cloud. Этот шаг необходим для поддержки функций генерации встраиваемых изображений и поиска векторов.
- Повторно разверните схему , чтобы активировать
pgvector
и поиск векторов, нажав «Развернуть в производстве» с помощью расширения Firebase Data Connect VS Code.
Заполнить базу данных встраиваниями
- Откройте папку
dataconnect
в VS Code. - Нажмите кнопку Запустить (локально) в
optional_vector_embed.gql
, чтобы заполнить базу данных встраиваниями для фильмов.
Добавить векторный поисковый запрос
В 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.
Реализуйте функцию поиска векторов в своем приложении
Теперь, когда схема и запрос настроены, интегрируйте векторный поиск в сервисный уровень вашего приложения. Это позволит вам вызывать поисковый запрос из вашего веб-приложения.
- В
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
, передавая входной текст для выполнения поиска контента на основе векторов.
Посмотрите на это в действии
Перейдите в раздел «Поиск векторных изображений» на панели навигации и введите фразы, например, «романтический и современный». Вы увидите список фильмов, соответствующих вашему запросу, или перейдите на страницу с описанием фильма и посмотрите раздел похожих фильмов внизу страницы.
12. Заключение
Поздравляем, теперь вы можете использовать веб-приложение! Если хотите работать с собственными данными фильмов, не волнуйтесь: вставьте их с помощью расширения Firebase Data Connect, имитируя файлы _insert.gql
, или добавьте их через панель выполнения Data Connect в VS Code.