運用 Firebase Data Connect 建構內容

1. 事前準備

FriendlyMovies 應用程式

在本程式碼研究室中,您將整合 Firebase Data Connect 與 Cloud SQL 資料庫,以建構電影評論網頁應用程式。完成的應用程式會展示 Firebase Data Connect 如何簡化建構 SQL 應用程式的工作流程。其中包含以下功能:

  • 驗證:為應用程式的查詢和突變導入自訂驗證機制,確保只有授權使用者能與資料互動。
  • GraphQL 架構:根據電影評論網頁應用程式的需求,使用靈活的 GraphQL 架構建立及管理資料結構。
  • SQL 查詢和變異式:使用 GraphQL 提供的查詢和變異式,擷取、更新及管理 Cloud SQL 中的資料。
  • 進階搜尋 (部分字串比對):使用篩選器和搜尋選項,根據標題、說明或標記等欄位搜尋電影。
  • 選用:向量搜尋整合:使用 Firebase Data Connect 的向量搜尋功能新增內容搜尋功能,根據輸入內容和偏好設定提供豐富的使用者體驗。

必要條件

您需要具備 JavaScript 的基本知識。

課程內容

  • 使用本機模擬器設定 Firebase Data Connect。
  • 使用 Data Connect 和 GraphQL 設計資料結構定義。
  • 為電影評論應用程式編寫及測試各種查詢和變異。
  • 瞭解 Firebase Data Connect 如何在應用程式中產生及使用 SDK。
  • 部署結構定義,並有效率地管理資料庫。

所需工具

設定開發環境

本節將引導您設定環境,以便開始使用 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 Code 中開啟專案

使用 Visual Studio Code 開啟複製的 codelab-dataconnect-web 資料夾。您可以在這個畫面中定義結構定義、編寫查詢,以及測試應用程式功能。

步驟 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 擴充功能中,按一下「Start Emulators」,並確認終端機中是否正在執行模擬器。

6d3d95f4cb708db1.png

2. 查看範例程式碼

在本節中,您將探索應用程式啟動程式碼集的幾個重點領域。雖然應用程式缺少部分功能,但有助於您瞭解整體結構。

資料夾和檔案結構

以下是應用程式資料夾和檔案結構的簡要說明:

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 應用程式的設定。
  • lib/dataconnect-sdk/:這個資料夾包含產生的 SDK。您可以在 connector/connector.yaml 檔案中編輯 SDK 產生位置,系統會在您定義查詢或 mutation 時自動產生 SDK。

3. 定義電影評論的結構定義

在本節中,您將在結構定義中定義電影應用程式中關鍵實體之間的關係。MovieUserActorReview 等實體會對應至資料庫表格,並使用 Firebase Data Connect 和 GraphQL 架構指令建立關係。完成後,應用程式就能處理所有相關作業,包括搜尋高評分電影、依類型篩選內容、讓使用者留下評論、標記喜愛的內容、探索類似電影,或透過向量搜尋功能,根據文字輸入內容找出推薦電影。

核心實體和關係

Movie 類型會保留標題、類型和標記等重要詳細資料,應用程式會用於搜尋和電影設定檔。User 類型會追蹤使用者互動,例如評論和收藏。Reviews 將使用者連結至電影,讓應用程式顯示使用者提供的評分和意見回饋。

電影、演員和使用者之間的關係,可讓應用程式更具動態性。MovieActor 彙整表格可協助顯示演員陣容詳細資料和演員的電影作品。FavoriteMovie 類型可讓使用者將電影設為喜愛項目,讓應用程式顯示個人化的喜愛項目清單,並突顯熱門選項。

Movie 表

電影類型會定義電影實體的主要結構,包括片名、類型、發行年份和評分等欄位。

複製程式碼片段,然後貼到 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:定義演員在電影中的角色 (例如 「主要」或「輔助」)。

使用者資料表

User 類型定義了使用者實體,這些使用者會透過留下評論或將電影設為喜好項目,與電影互動。

複製程式碼片段,然後貼到 dataconnect/schema/schema.gql 檔案中:

type User
  @table {
  id: String! @col(name: "auth_uid")
  username: String! @col(dataType: "varchar(50)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user 
  # movies_via_Review
}

FavoriteMovie 資料表

FavoriteMovie 類型是彙整表,可處理使用者與其喜愛的電影或演員之間的多對多關係。每個表格都會將 User 連結至 Movie

複製程式碼片段,然後貼到 dataconnect/schema/schema.gql 檔案中:

type FavoriteMovie
  @table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
  # @ref is implicit
  user: User!
  movie: Movie!
}

重點整理:

  • movie:參照 Movie 類型,會隱含產生外鍵 movieId: UUID!。
  • user:參照使用者類型,會隱含產生外鍵 userId: UUID!。

評論表

「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")
}

重點整理:

  • user: 參照留下評論的使用者。
  • movie: 參照正在評論的電影。
  • reviewDate:自動設為使用 @default(expr: "request.time") 建立評論的時間。

自動產生的欄位和預設值

這個結構定義會使用 @default(expr: "uuidV4()") 等運算式,自動產生不重複的 ID 和時間戳記。舉例來說,建立新記錄時,Movie 和 Review 類型的 id 欄位會自動填入 UUID。

架構定義完成後,電影應用程式就能建立穩固的資料結構和關係!

4. 擷取熱門和最新電影

FriendlyMovies 應用程式

在本節中,您將在本機模擬器中插入模擬電影資料,然後實作連接器 (查詢) 和 TypeScript 程式碼,以便在網頁應用程式中呼叫這些連接器。完成後,應用程式就能直接從資料庫動態擷取及顯示最高評分和最新的電影。

插入模擬電影、演員和評論資料

  1. 在 VSCode 中開啟 dataconnect/moviedata_insert.gql。確認 Firebase Data Connect 擴充功能中的模擬器是否正在執行。
  2. 檔案頂端應該會顯示「Run (local)」按鈕。按一下這個按鈕,即可將模擬電影資料插入資料庫。

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

這項查詢會擷取所有電影及其詳細資料 (例如 ID、標題、發行年份)。不過,系統不會排序電影。

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

按一下「Run (local)」按鈕,對本機資料庫執行查詢。您也可以在執行前,在設定窗格中輸入查詢變數。

c4d947115bb11b16.png

重點整理:

  • movies(): 用於從資料庫擷取電影資料的 GraphQL 查詢欄位。
  • orderByRating:用於依評分排序電影的參數 (遞增/遞減)。
  • orderByReleaseYear:依電影上映年份排序的參數 (遞增/遞減)。
  • limit:限制傳回的電影數量。

在網路應用程式中整合查詢

在本節中,您將在網頁應用程式中使用前一個部分定義的查詢。Firebase Data Connect 模擬器會根據 .gql 檔案 (schema.gql、queries.gql、mutations.gql) 和 connector.yaml 中的資訊產生 SDK。這些 SDK 可直接在應用程式中呼叫。

  1. MovieService (app/src/lib/MovieService.tsx) 中,將頂端的匯入陳述式取消註解
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

函式 listMovies、回應類型 ListMoviesData 和列舉 OrderDirection 都是 Firebase Data Connect 模擬器根據您先前定義的結構定義和查詢所產生的 SDK。

  1. 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:用於在首頁顯示前 10 部熱門電影和最新電影的結果類型。

實例觀摩

重新載入網路應用程式,即可查看查詢的運作情形。首頁現在會動態顯示電影清單,並直接從本機資料庫擷取資料。系統會根據你剛剛設定的資料,流暢地顯示評價最高和最新的電影。

5. 顯示電影和演員詳細資料

在本節中,您將實作功能,以便使用電影或演員的專屬 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
    }
  }
}
  1. 儲存變更並查看查詢。

重點整理:

  • movie()/actor():GraphQL 查詢欄位,用於從「電影」或「演員」資料表擷取單一電影或演員。
  • _on_:可直接存取具有外鍵關係的關聯類型欄位。舉例來說,reviews_on_movie 會擷取與特定電影相關的所有評論。
  • _via_:用於透過彙整表格瀏覽多對多關係。舉例來說,actors_via_MovieActor 會透過 MovieActor 彙整表存取 Actor 類型,而 where 條件會根據角色 (例如 「主要」或「支援」)。

在 Data Connect 執行作業窗格中,您可以輸入模擬 ID 來測試查詢,例如:

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

按一下 GetMovieById 的「Run (local)」,擷取「Quantum Paradox」的詳細資料 (與上述 ID 相關的模擬電影)。

1b08961891e44da2.png

在網路應用程式中整合查詢

  1. MovieService (app/src/lib/MovieService.tsx) 中,將下列匯入作業取消註解
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. 取代 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:這些是結果類型,用於在應用程式中顯示電影和演員詳細資料。

實例觀摩

接著,前往網頁應用程式的首頁。點選電影後,您就能查看所有詳細資料,包括演員和評論等資訊,這些資訊都是從相關資料表中擷取。同樣地,點選演員後,系統也會顯示他們參與的電影。

6. 處理使用者驗證

在本節中,您將使用 Firebase 驗證功能實作使用者登入和登出功能。您也可以使用 Firebase 驗證資料,直接在 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,而非使用者或應用程式提供的 auth.uid,可確保使用者 ID 安全且自動處理,進而提供額外的安全防護。

接著,請在 dataconnect/movie-connector/開啟 queries.gql

新增下列查詢,擷取目前使用者:

# Get user by ID
query GetCurrentUser @auth(level: USER) {
  user(key: { id_expr: "auth.uid" }) {
    id
    username
    reviews: reviews_on_user {
      id
      rating
      reviewDate
      reviewText
      movie {
        id
        title
      }
    }
    favoriteMovies: favorite_movies_on_user {
      movie {
        id
        title
        genre
        imageUrl
        releaseYear
        rating
        description
        tags
        metadata: movieMetadatas_on_movie {
          director
        }
      }
    }
  }
}

重點摘要:

  • auth.uid:這是直接從 Firebase 驗證中擷取,可確保安全存取使用者專屬資料。
  • _on_ 欄位:這些欄位代表彙整表格:
  • reviews_on_user:擷取與使用者相關的所有評論,包括電影的 ID 和標題。
  • favorite_movies_on_user:擷取使用者將其標示為常用項目的所有電影,包括類型、發行年份、評分和中繼資料等詳細資訊。

在網頁應用程式中整合查詢

  1. 在 MovieService (app/src/lib/MovieService.tsx) 中,取消註解以下匯入項目:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. 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 驗證模擬器登入。登入後,按一下「我的個人資料」。這個資料夾目前會是空白,但您已為應用程式中的使用者資料處理作業奠定基礎。

7. 實作使用者互動

在本節中,您將在電影評論應用程式中實作使用者互動,讓使用者管理喜愛的電影,並留下或刪除評論。

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":使用 auth.uid,這是由 Firebase 驗證直接提供,可確保只存取或修改已驗證使用者的資料。
  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 驗證,確保可安全存取特定使用者資料。
  • favorite_movie:檢查 favorite_movies 彙整表,查看目前使用者是否將特定電影標示為收藏。

在網路應用程式中整合查詢

  1. MovieService (app/src/lib/MovieService.tsx) 中,將下列匯入作業取消註解
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. 取代 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 查詢,檢查使用者是否將電影標示為收藏。

實例觀摩

現在只要在電影資訊卡和電影詳細資料頁面中點選愛心圖示,即可將電影加入或移出收藏清單。此外,你也可以在個人資料頁面上查看喜愛的電影。

導入使用者評論

接下來,您將在應用程式中實作管理使用者評論的部分。

實作連接器

  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. 取代 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 異動來移除已驗證使用者對電影的評論。

實例觀摩

使用者現在可以在電影詳細資料頁面上為電影撰寫評論。使用者也可以在個人資料頁面上查看及刪除自己的評論,完全掌控與應用程式的互動情形。

8. 進階篩選器和部分文字比對

在本節中,您將實作進階搜尋功能,讓使用者可根據一系列評分和發行年份搜尋電影、依類型和標記篩選、在標題或說明中執行部分文字比對,甚至結合多個篩選器,以便取得更精確的結果。

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 子句,定義搜尋的特定條件。

在網路應用程式中整合查詢

  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 CLI 執行這項操作。

在 Firebase 控制台中新增網頁應用程式

  1. Firebase 主控台建立網頁應用程式,並記下應用程式 ID

7030822793e4d75b.png

  1. 按一下「Add App」(新增應用程式),在 Firebase 主控台中設定網頁應用程式。您可以暫時忽略 SDK 設定和配置設定,但請記下產生的 firebaseConfig 物件。
  2. 取代 app/src/lib/firebase.tsx 中的 firebaseConfig
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. 使用 Google 登入功能設定 Firebase 驗證

62af2f225e790ef6.png

  1. (選用) 在專案控制台中允許 (Firebase 驗證) [https://firebase.google.com/docs/auth/web/hosting] 的網域 (例如http://127.0.0.1):

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"]
  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 for PostgreSQL 例項。您可以在 Firebase 主控台中監控狀態。

您現在應該可以在 your-project.web.app/ 上看到應用程式。此外,您也可以在 Firebase Data Connect 面板中按一下「Run (Production)」,就像使用本機模擬器一樣,將資料新增至實際工作環境。

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. 請按照前置條件指南設定 Google Cloud 的 Vertex AI API。這個步驟對於支援嵌入產生和向量搜尋功能至關重要。
  2. 重新部署結構定義:使用 Firebase Data Connect VSCode 擴充功能,按一下「部署至正式版」即可啟用 pgvector 和向量搜尋功能。

使用嵌入內容填入資料庫

  1. 開啟 VSCode 中的 dataconnect 資料夾,然後按一下 optional_vector_embed.gql 中的「Run(local)」,將電影的嵌入資料填入資料庫。

b858da780f6ec103.png

新增 Vector Search 查詢

  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. 結論

恭喜,您應該可以使用網頁應用程式了!如果你想使用自己的電影資料,別擔心,只要模仿 _insert.gql 檔案,就能使用 FDC 擴充功能插入自己的資料,或是透過資料連結執行窗格新增資料。

瞭解詳情