使用 Firebase Data Connect 进行构建

1. 准备工作

FriendlyMovies 应用

在此 Codelab 中,您将 Firebase Data Connect 与 Cloud SQL 数据库集成,以构建电影评价 Web 应用。完成的应用展示了 Firebase Data Connect 如何简化构建依托 SQL 的应用的过程。它包含以下功能:

  • 身份验证:为应用的查询和更改实现自定义身份验证,确保只有已获授权的用户才能与您的数据互动。
  • GraphQL 架构:使用专为电影评论 Web 应用需求量身定制的灵活 GraphQL 架构创建和管理数据结构。
  • SQL 查询和更改:使用由 GraphQL 提供支持的查询和更改,在 Cloud SQL 中检索、更新和管理数据。
  • 支持部分字符串匹配的高级搜索:使用过滤条件和搜索选项,根据标题、说明或标签等字段查找电影。
  • (可选) 向量搜索集成:使用 Firebase Data Connect 的向量搜索功能添加内容搜索功能,以便根据输入和偏好设置提供丰富的用户体验。

前提条件

您需要对 JavaScript 有基本的了解。

学习内容

  • 使用本地模拟器设置 Firebase Data Connect。
  • 使用 Data Connect 和 GraphQL 设计数据架构。
  • 为影评应用编写和测试各种查询和更改。
  • 了解 Firebase Data Connect 如何在应用中生成和使用 SDK。
  • 高效部署架构并管理数据库。

所需条件

2. 设置您的开发环境

此 Codelab 的此阶段将引导您设置环境,以便开始使用 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,即可查看本地运行的 Web 应用。它将用作构建电影评价应用和与其功能互动的前端。93f6648a2532c606.png
  3. 使用 Visual Studio Code 打开克隆的 codelab-dataconnect-web 文件夹。您将在此处定义架构、编写查询并测试应用的功能。
  4. 如需使用 Data Connect 功能,请安装 Firebase Data Connect Visual Studio 扩展程序
    或者,您也可以从 Visual Studio Code Marketplace 安装该扩展程序,或在 VS Code 中搜索该扩展程序。b03ee38c9a81b648.png
  5. Firebase 控制台中打开或创建一个新的 Firebase 项目。
  6. 将您的 Firebase 项目与 Firebase Data Connect VSCode 扩展程序相关联。在扩展程序中,执行以下操作:
    1. 点击登录按钮。
    2. 点击关联 Firebase 项目,然后选择您的 Firebase 项目。
    4bb2fbf8f9fac29b.png
  7. 使用 Firebase Data Connect VS Code 扩展程序启动 Firebase 模拟器:
    点击 Start Emulators(启动模拟器),然后确认模拟器是否在终端中运行。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。您可以在 connector/connector.yaml 文件中修改 SDK 生成的位置,并且每当您定义查询或更改时,系统都会自动生成 SDK。

4. 为电影评价定义架构

在本部分中,您将在架构中定义电影应用中关键实体的结构和这些实体之间的关系。MovieUserActorReview 等实体会映射到数据库表,并使用 Firebase Data Connect 和 GraphQL 架构指令建立关系。完成设置后,您的应用将能够处理各种操作,从搜索评分最高的电影和按类型过滤,到让用户发表评价、标记收藏、探索类似电影,或者通过向量搜索根据文本输入内容查找推荐电影。

核心实体和关系

Movie 类型用于存储标题、类型和标签等关键详细信息,这些信息会被应用用于搜索和电影个人资料。User 类型用于跟踪用户互动,例如评价和收藏。Reviews 将用户与电影相关联,让应用显示用户生成的评分和反馈。

电影、演员和用户之间的关系让应用变得更加动态。MovieActor 联接表有助于显示演员阵容详情和演员的电影作品目录。FavoriteMovie 类型允许用户将电影设为收藏,以便应用显示个性化的收藏列表并突出显示热门选段。

设置 Movie 表格

Movie 类型定义了电影实体的主要结构,包括 titlegenrereleaseYearrating 等字段。

将代码段复制并粘贴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"
}

要点总结

  • movie:引用 Movie 类型,隐式生成外键 movieId: UUID!。
  • actor:引用 Actor 类型,隐式生成外键 actorId: UUID!。
  • role:定义演员在电影中的角色(例如“main”或“supporting”)。

设置 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 类型是一个联接表,用于处理用户与其喜爱的电影之间的多对多关系。每个表都会将 UserMovie 相关联。

将代码段复制并粘贴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 类型代表评价实体,并以多对多关系UserMovie 类型相关联(一位用户可以发表多个评价,每部电影也可以有多个评价)。

将代码段复制并粘贴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")
}

要点总结

  • user:引用留下评价的用户。
  • movie:引用正在评价的电影。
  • reviewDate:自动设置为使用 @default(expr: "request.time") 创建评价的时间。

自动生成的字段和默认值

该架构使用 @default(expr: "uuidV4()") 等表达式自动生成唯一 ID 和时间戳。例如,在创建新记录时,MovieReview 类型中的 id 字段会自动填充 UUID。

现在,架构已定义完毕,您的电影应用的数据结构和关系已打下坚实的基础!

5. 检索热门电影和最新电影

FriendlyMovies 应用

在本部分中,您将将模拟的电影数据插入本地模拟器,然后实现连接器(查询)和 TypeScript 代码,以便在 Web 应用中调用这些连接器。最后,您的应用将能够直接从数据库中动态提取并显示评分最高和最新的电影。

插入模拟的电影、演员和评价数据

  1. 在 VSCode 中,打开 dataconnect/moviedata_insert.gql。确保 Firebase Data Connect 扩展程序中的模拟器正在运行。
  2. 您应该会在文件顶部看到 Run (local) 按钮。点击此按钮可将模拟电影数据插入数据库。
    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
      }
    }
    
    此查询会提取所有电影及其详细信息(例如 idtitlereleaseYear)。不过,它不会对电影进行排序。
  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. 点击 Run (local) 按钮,对本地数据库执行查询。您也可以在运行前在“配置”窗格中输入查询变量。
    c4d947115bb11b16.png

要点总结

  • movies():用于从数据库中提取电影数据的 GraphQL 查询字段。
  • orderByRating:用于按评分对电影进行排序的参数(升序/降序)。
  • orderByReleaseYear:用于按发行年份对电影进行排序的参数(升序/降序)。
  • limit:限制返回的电影数量。

在 Web 应用中集成查询

在本 Codelab 的此部分,您将在 Web 应用中使用上一部分中定义的查询。Firebase Data Connect 模拟器会根据 .gql 文件(具体而言是 schema.gqlqueries.gqlmutations.gql)和 connector.yaml 文件中的信息生成 SDK。您可以在应用中直接调用这些 SDK。

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释顶部的 import 语句:
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    函数 listMovies、响应类型 ListMoviesData 和枚举 OrderDirection 都是 Firebase Data Connect 模拟器根据您之前定义的架构和查询生成的 SDK。
  2. handleGetTopMovieshandleGetLatestMovies 函数替换为以下代码:
    // 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:用于在应用首页上显示十大热门影片和最新影片的结果类型。

看看实际运用情况

重新加载 Web 应用,查看查询的实际运作情况。首页现在会动态显示电影列表,并直接从本地数据库中提取数据。您会看到系统会顺畅地显示评分最高的电影和最新电影,这些电影反映了您刚刚设置的数据。

6. 显示电影和演员详情

在本部分中,您将实现使用电影或演员的唯一 ID 检索其详细信息的功能。这不仅涉及从各自的表中提取数据,还涉及联接相关表以显示全面的详细信息,例如电影评价和演员的电影作品目录。

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():用于从 MoviesActors 表中提取单个电影或演员的 GraphQL 查询字段。
  • _on_:这样,您就可以直接访问具有外键关系的关联类型中的字段。例如,reviews_on_movie 会提取与特定电影相关的所有评价。
  • _via_:用于通过联接表浏览多对多关系。例如,actors_via_MovieActor 通过 MovieActor 联接表访问 Actor 类型,where 条件会根据演员的角色(例如“主演”或“配角”)过滤演员。

通过输入模拟数据来测试查询

  1. 在 Data Connect 执行窗格中,您可以通过输入模拟 ID 来测试查询,例如:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. 点击 GetMovieById运行(本地),即可检索“Quantum Paradox”(上述 ID 对应的模拟电影)的详细信息。

1b08961891e44da2.png

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. handleGetMovieByIdhandleGetActorById 函数替换为以下代码:
    // 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:这些是结果类型,用于在应用中显示电影和演员详细信息。

看看实际运用情况

现在,前往您的 Web 应用的首页。点击一部电影,您就可以查看其所有详细信息,包括演员和评价(这些信息是从相关表中提取的)。同样,点击某位演员即可显示其出演的电影。

7. 处理用户身份验证

在本部分中,您将使用 Firebase Authentication 实现用户登录和退出功能。您还将使用 Firebase Authentication 数据直接在 Firebase DataConnect 中检索或更新/插入用户数据,从而确保在应用中安全地管理用户。

9890838045d5a00e.png

实现连接器

  1. 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 Authentication 直接提供,而不是由用户或应用提供,从而确保用户 ID 安全且自动地得到处理,从而增强了安全性。

提取当前用户

  1. dataconnect/movie-connector/打开 queries.gql
  2. 添加以下查询以提取当前用户:
    # Get user by ID
    query GetCurrentUser @auth(level: USER) {
      user(key: { id_expr: "auth.uid" }) {
        id
        username
        reviews: reviews_on_user {
          id
          rating
          reviewDate
          reviewText
          movie {
            id
            title
          }
        }
        favoriteMovies: favorite_movies_on_user {
          movie {
            id
            title
            genre
            imageUrl
            releaseYear
            rating
            description
            tags
            metadata: movieMetadatas_on_movie {
              director
            }
          }
        }
      }
    }
    

要点总结

  • auth.uid:此值会直接从 Firebase Authentication 检索,以确保安全地访问用户专用数据。
  • _on_ 字段:这些字段代表联接表:
    • reviews_on_user:提取与用户相关的所有评价,包括电影的 idtitle
    • favorite_movies_on_user:检索用户标记为收藏的所有电影,包括 genrereleaseYearratingmetadata 等详细信息。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. handleAuthStateChangehandleGetCurrentUser 函数替换为以下代码:
    // 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 Authentication 模拟器进行登录。登录后,点击“我的个人资料”。它目前是空的,但您已为在应用中处理特定于用户的数据奠定了基础。

8. 实现用户互动

在此 Codelab 的此部分中,您将在电影评价应用中实现用户互动,具体而言,让用户管理喜爱的电影,以及发表或删除评价。

b3d0ac1e181c9de9.png

允许用户将电影设为收藏

在本部分中,您将设置数据库,以允许用户将电影添加为收藏。

实现连接器

  1. 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":使用由 Firebase Authentication 直接提供的 auth.uid,确保仅访问或修改经过身份验证的用户的数据。

检查电影是否已收藏

  1. dataconnect/movie-connector/打开 queries.gql
  2. 添加以下查询,以检查电影是否已收藏:
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

要点总结

  • auth.uid:确保使用 Firebase Authentication 安全地访问用户专用数据。
  • favorite_movie:检查 favorite_movies 联接表,以确定当前用户是否已将特定电影标记为收藏。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
    import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
    
  2. handleAddFavoritedMoviehandleDeleteFavoritedMoviehandleGetIfFavoritedMovie 函数替换为以下代码:
    // 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;
      }
    };
    

要点总结

  • handleAddFavoritedMoviehandleDeleteFavoritedMovie:使用这些更改操作可安全地将电影添加或移除到用户的收藏夹中。
  • 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 自动为评价生成当前日期,从而无需手动输入。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. handleAddReviewhandleDeleteReview 函数替换为以下代码:
    // 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. dataconnect/movie-connector/ 中打开 queries.gql
  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 运算符:在单个查询中组合多个条件,以便按多个字段(例如 releaseYearratinggenre)过滤搜索结果。
  • contains 运算符:搜索字段中的部分文本匹配项。在此查询中,它会在 titledescriptionnamereviewText 中查找匹配项。
  • where 子句:指定用于过滤数据的条件。每个版块(电影、演员、影评)都使用 where 子句来定义搜索的具体条件。

在 Web 应用中集成查询

  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. 可选:部署到 Cloud(需要结算功能)

现在,您已经完成了本地开发迭代,接下来需要将架构、数据和查询部署到服务器。您可以使用 Firebase Data Connect VS Code 扩展程序或 Firebase CLI 来执行此操作。

升级您的 Firebase 定价方案

如需将 Firebase Data Connect 与 Cloud SQL for PostgreSQL 集成,您的 Firebase 项目必须采用随用随付 (Blaze) 定价方案,这意味着该项目需要与一个 Cloud Billing 账号相关联。

  • Cloud Billing 账号要求提供付款方式,例如信用卡。
  • 如果您刚开始接触 Firebase 和 Google Cloud,请确认您是否有资格获得 $300 赠金和免费试用 Cloud Billing 账号
  • 如果您是在某个活动中学习本 Codelab,请询问组织者是否有 Cloud 抵用金。

如需将项目升级到 Blaze 方案,请按以下步骤操作:

  1. 在 Firebase 控制台中,选择升级您的方案
  2. 选择 Blaze 方案。按照屏幕上的说明将 Cloud Billing 账号与您的项目相关联。
    如果您需要在此升级过程中创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。

将您的 Web 应用与 Firebase 项目相关联

  1. 使用 Firebase 控制台在 Firebase 项目中注册您的 Web 应用:
    1. 打开您的项目,然后点击添加应用
    2. 暂时忽略 SDK 设置和配置设置,但请务必复制生成的 firebaseConfig 对象。
    7030822793e4d75b.png
  2. app/src/lib/firebase.tsx 中的现有 firebaseConfig 替换为您刚刚从 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. 构建 Web 应用:返回 VS Code,在 app 文件夹中,使用 Vite 构建 Web 应用以进行托管部署:
    cd app
    npm run build
    

在 Firebase 项目中设置 Firebase Authentication

  1. 设置 Firebase Authentication 与 Google 登录。62af2f225e790ef6.png
  2. (可选)使用 Firebase 控制台为 Firebase Authentication 授予网域访问权限(例如 http://127.0.0.1)。
    1. 身份验证设置中,前往已获授权的网域
    2. 点击“添加网域”,然后在列表中添加您的本地网域。

c255098f12549886.png

使用 Firebase CLI 进行部署

  1. dataconnect/dataconnect.yaml 中,确保您的实例 ID、数据库和服务 ID 与您的项目一致:
    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 for PostgreSQL 实例将更新为包含最终部署的架构和数据。您可以在 Firebase 控制台中监控状态。

现在,您应该可以在 your-project.web.app/ 上看到您的应用。此外,您还可以点击 Firebase Data Connect 面板中的运行(生产),就像使用本地模拟器一样,将数据添加到生产环境。

11. 可选:使用 Firebase Data Connect 进行向量搜索(需要启用结算功能)

在本部分中,您将使用 Firebase Data Connect 在电影评价应用中启用矢量搜索。借助此功能,您可以进行基于内容的搜索,例如使用向量嵌入查找描述类似的电影。

若要完成此步骤,您必须先完成此 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):此字段用于存储电影说明的语义嵌入,以便在应用中实现基于向量的内容搜索。

启用 Vertex AI

  1. 按照前提条件指南中的说明,通过 Google Cloud 设置 Vertex AI API。此步骤对于支持嵌入生成和向量搜索功能至关重要。
  2. 使用 Firebase Data Connect VS Code 扩展程序,点击“部署到生产环境”以重新部署架构,以启用 pgvector 和向量搜索。

使用嵌入填充数据库

  1. 在 VS Code 中打开 dataconnect 文件夹。
  2. 点击 optional_vector_embed.gql 中的 Run(local),使用电影的嵌入向数据库中填充数据。

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。

在应用中实现向量搜索函数

现在,架构和查询已设置完毕,接下来将矢量搜索集成到应用的服务层中。通过此步骤,您可以从 Web 应用调用搜索查询。

  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. 总结

恭喜,您应该可以使用 Web 应用了!如果您想使用自己的电影数据,请放心,您可以通过模仿 _insert.gql 文件使用 Firebase Data Connect 扩展程序插入自己的数据,也可以通过 VS Code 中的 Data Connect 执行窗格添加这些数据。

了解详情