Firebase Data Connect を使用して構築する

1. 始める前に

FriendlyMovies アプリ

この Codelab では、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 を生成し、使用する仕組みについて学びます。
  • スキーマをデプロイして、データベースを効率的に管理します。

必要なもの

  • Git
  • Visual Studio Code
  • nvm-windows(Windows)または nvm(macOS/Linux)を使用して Node.js をインストールします。
  • まだ作成していない場合は、Firebase コンソールで Firebase プロジェクトを作成します。
  • (省略可)ベクトル検索の場合は、プロジェクトを従量課金制の Blaze のお支払いプランにアップグレードします。

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 を開き、ローカルで実行されているウェブアプリを確認します。これは、映画レビュー アプリを構築し、その機能を操作するためのフロントエンドとして機能します。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 プロジェクトを開くか、新しい Firebase プロジェクトを作成します。
  6. Firebase プロジェクトを Firebase Data Connect VSCode 拡張機能に接続します。拡張機能で、次の操作を行います。
    1. [ログイン] ボタンをクリックします。
    2. [Firebase プロジェクトを接続] をクリックして、Firebase プロジェクトを選択します。
    4bb2fbf8f9fac29b.png
  7. Firebase Data Connect VS Code 拡張機能を使用して Firebase エミュレータを起動します。
    [エミュレータを起動] をクリックし、エミュレータがターミナルで実行されていることを確認します。6d3d95f4cb708db1.png

3. スターター コードベースを確認する

このセクションでは、アプリのスターター コードベースの主要な部分について説明します。このアプリには一部の機能が欠落していますが、全体的な構造を理解するのに役立ちます。

フォルダとファイルの構造

次のサブセクションでは、アプリのフォルダとファイル構造の概要について説明します。

dataconnect/ ディレクトリ

Firebase Data Connect の構成、コネクタ(クエリとミューテーションを定義)、スキーマ ファイルが含まれています。

  • schema/schema.gql: GraphQL スキーマを定義します。
  • connector/queries.gql: アプリで必要なクエリ
  • connector/mutations.gql: アプリで必要なミューテーション
  • connector/connector.yaml: SDK 生成の構成ファイル

app/src/ ディレクトリ

アプリケーション ロジックと Firebase Data Connect とのやり取りが含まれます。

  • firebase.ts: Firebase プロジェクト内の Firebase アプリに接続するための構成。
  • lib/dataconnect-sdk/: 生成された SDK が含まれています。SDK 生成の場所は connector/connector.yaml ファイルで編集できます。クエリまたはミューテーションを定義するたびに SDK が自動的に生成されます。

4. 映画レビューのスキーマを定義する

このセクションでは、映画アプリの主要なエンティティの構造と関係をスキーマで定義します。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: @default(expr: "uuidV4()") を使用して生成される、映画ごとに一意の UUID。

MovieMetadata テーブルを設定する

MovieMetadata 型は、Movie 型と1 対 1 の関係を確立します。映画の監督などの追加データが含まれます。

コード スニペットを 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 タイプはレビュー エンティティを表し、User タイプと Movie タイプを多対多の関係でリンクします(1 人のユーザーが複数のレビューを投稿でき、各映画に複数のレビューを投稿できます)。

コード スニペットを 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 が自動的に入力されます。

スキーマが定義されたので、映画アプリのデータ構造と関係の基盤ができました。

5. 人気映画と最新映画を取得する

FriendlyMovies アプリ

このセクションでは、ローカル エミュレータに映画のモックデータを挿入してから、コネクタ(クエリ)と TypeScript コードを実装し、ウェブアプリでこれらのコネクタを呼び出します。最終的には、アプリでデータベースから直接、評価の高い最新の映画を動的に取得して表示できるようになります。

映画、俳優、レビューの架空のデータを挿入する

  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. [実行(ローカル)] ボタンをクリックして、ローカル データベースに対してクエリを実行します。実行前に、構成ペインでクエリ変数を入力することもできます。
    c4d947115bb11b16.png

重要ポイント:

  • movies(): データベースから映画データを取得する GraphQL クエリ フィールド。
  • orderByRating: 映画を評価(昇順/降順)で並べ替えるパラメータ。
  • orderByReleaseYear: 映画を公開年(昇順/降順)で並べ替えるパラメータ。
  • limit: 返される映画の数を制限します。

ウェブアプリにクエリを統合する

この Codelab のパートでは、前のセクションで定義したクエリをウェブアプリで使用します。Firebase Data Connect エミュレータは、.gql ファイル(特に schema.gqlqueries.gqlmutations.gql)と connector.yaml ファイルの情報に基づいて SDK を生成します。これらの SDK は、アプリ内で直接呼び出すことができます。

  1. MovieServiceapp/src/lib/MovieService.tsx)で、先頭の import ステートメントのコメント化を解除します。
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    関数 listMovies、レスポンス タイプ ListMoviesData、列挙型 OrderDirection はすべて、以前に定義したスキーマとクエリに基づいて Firebase Data Connect エミュレータによって生成された SDK です。
  2. handleGetTopMovies 関数と handleGetLatestMovies 関数を次のコードに置き換えます。
    // Fetch top-rated movies
    export const handleGetTopMovies = async (
      limit: number
    ): Promise<ListMoviesData["movies"] | null> => {
      try {
        const response = await listMovies({
          orderByRating: OrderDirection.DESC,
          limit,
        });
        return response.data.movies;
      } catch (error) {
        console.error("Error fetching top movies:", error);
        return null;
      }
    };
    
    // Fetch latest movies
    export const handleGetLatestMovies = async (
      limit: number
    ): Promise<ListMoviesData["movies"] | null> => {
      try {
        const response = await listMovies({
          orderByReleaseYear: OrderDirection.DESC,
          limit,
        });
        return response.data.movies;
      } catch (error) {
        console.error("Error fetching latest movies:", error);
        return null;
      }
    };
    

重要ポイント:

  • listMovies: listMovies クエリを呼び出して映画のリストを取得する自動生成関数。評価や公開年で並べ替えたり、結果の数を制限したりするオプションがあります。
  • ListMoviesData: アプリのホームページにトップ 10 と最新の映画を表示するために使用される結果タイプ。

実例を見る

ウェブアプリを再読み込みして、クエリが実行されていることを確認します。ホームページに映画のリストが動的に表示され、ローカル データベースから直接データが取得されるようになりました。設定したデータが反映され、高評価の映画や最新の映画がシームレスに表示されます。

6. 映画と俳優の詳細を表示する

このセクションでは、一意の 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(): Movies テーブルまたは Actors テーブルから 1 本の映画または俳優を取得するための GraphQL クエリ フィールド。
  • _on_: これにより、外部キー関係を持つ関連する型のフィールドに直接アクセスできます。たとえば、reviews_on_movie は特定の映画に関連するすべてのレビューを取得します。
  • _via_: 結合テーブルを介して多対多の関係を移動するために使用されます。たとえば、actors_via_MovieActorMovieActor 結合テーブルを介して Actor タイプにアクセスし、where 条件は役割(「メイン」や「サポート」など)に基づいて俳優をフィルタします。

モックデータを入力してクエリをテストする

  1. Data Connect 実行ペインで、次のようなモック ID を入力してクエリをテストできます。
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. GetMovieById の [実行(ローカル)] をクリックして、「Quantum Paradox」(上記の ID が関連付けられているモック映画)の詳細を取得します。

1b08961891e44da2.png

ウェブアプリにクエリを統合する

  1. MovieServiceapp/src/lib/MovieService.tsx)で、次のインポートをコメント化解除します。
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. handleGetMovieById 関数と handleGetActorById 関数を次のコードに置き換えます
    // Fetch movie details by ID
    export const handleGetMovieById = async (
      movieId: string
    ) => {
      try {
        const response = await getMovieById({ id: movieId });
        if (response.data.movie) {
          return response.data.movie;
        }
        return null;
      } catch (error) {
        console.error("Error fetching movie:", error);
        return null;
      }
    };
    
    // Calling generated SDK for GetActorById
    export const handleGetActorById = async (
      actorId: string
    ): Promise<GetActorByIdData["actor"] | null> => {
      try {
        const response = await getActorById({ id: actorId });
        if (response.data.actor) {
          return response.data.actor;
        }
        return null;
      } catch (error) {
        console.error("Error fetching actor:", error);
        return null;
      }
    };
    

重要ポイント:

  • getMovieById / getActorById: 定義したクエリを呼び出して、特定の映画や俳優の詳細情報を取得する自動生成関数です。
  • GetMovieByIdData / GetActorByIdData: 映画や俳優の詳細をアプリに表示するために使用される結果タイプです。

実例を見る

ウェブアプリのホームページに移動します。映画をクリックすると、関連テーブルから取得された俳優やレビューなど、映画の詳細情報がすべて表示されます。同様に、俳優をクリックすると、その俳優が出演した映画が表示されます。

7. ユーザー認証を処理する

このセクションでは、Firebase 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 などの詳細情報も取得します。

ウェブアプリにクエリを統合する

  1. MovieServiceapp/src/lib/MovieService.tsx)で、次のインポートをコメント化解除します。
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. handleAuthStateChange 関数と handleGetCurrentUser 関数を次のコードに置き換えます。
    // Handle user authentication state changes and upsert user
    export const handleAuthStateChange = (
      auth: any,
      setUser: (user: User | null) => void
    ) => {
      return onAuthStateChanged(auth, async (user) => {
        if (user) {
          setUser(user);
          const username = user.email?.split("@")[0] || "anon";
          await upsertUser({ username });
        } else {
          setUser(null);
        }
      });
    };
    
    // Fetch current user profile
    export const handleGetCurrentUser = async (): Promise<
      GetCurrentUserData["user"] | null
    > => {
      try {
        const response = await getCurrentUser();
        return response.data.user;
      } catch (error) {
        console.error("Error fetching user profile:", error);
        return null;
      }
    };
    

重要ポイント:

  • handleAuthStateChange: この関数は、認証状態の変化をリッスンします。ユーザーがログインすると、ユーザーのデータが設定され、upsertUser ミューテーションが呼び出されて、データベース内のユーザー情報が作成または更新されます。
  • handleGetCurrentUser: getCurrentUser クエリを使用して現在のユーザーのプロフィールを取得します。これにより、ユーザーのクチコミとお気に入りの映画が取得されます。

実例を見る

ナビゲーション バーの [Google でログイン] ボタンをクリックします。Firebase 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 結合テーブルをチェックし、特定の映画が現在のユーザーによってお気に入りとしてマークされているかどうかを確認します。

ウェブアプリにクエリを統合する

  1. MovieServiceapp/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.gqldataconnect/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. MovieServiceapp/src/lib/MovieService.tsx)で、次のインポートをコメント化解除します。
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. handleAddReview 関数と handleDeleteReview 関数を次のコードに置き換えます
    // Add a review to a movie
    export const handleAddReview = async (
      movieId: string,
      rating: number,
      reviewText: string
    ): Promise<void> => {
      try {
        await addReview({ movieId, rating, reviewText });
      } catch (error) {
        console.error("Error adding review:", error);
        throw error;
      }
    };
    
    // Delete a review from a movie
    export const handleDeleteReview = async (movieId: string): Promise<void> => {
      try {
        await deleteReview({ movieId });
      } catch (error) {
        console.error("Error deleting review:", error);
        throw error;
      }
    };
    

重要ポイント:

  • handleAddReview: addReview ミューテーションを呼び出して、指定した映画のレビューを追加し、認証済みユーザーに安全にリンクします。
  • handleDeleteReview: deleteReview ミューテーションを使用して、認証済みユーザーによる映画のレビューを削除します。

実例を見る

ユーザーは映画の詳細ページで映画のレビューを投稿できるようになりました。また、プロフィール ページでレビューを表示、削除することもできます。これにより、アプリとのやり取りを完全に管理できます。

9. 高度なフィルタと部分一致

このセクションでは、ユーザーが評価や公開年に基づいて映画を検索したり、ジャンルやタグでフィルタしたり、タイトルや説明で部分一致検索を行ったり、複数のフィルタを組み合わせてより正確な結果を得たりできる高度な検索機能を実装します。

ece70ee0ab964e28.png

コネクタを実装する

  1. 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 演算子: 複数の条件を 1 つのクエリに結合し、releaseYearratinggenre などの複数のフィールドで検索をフィルタできます。
  • contains 演算子: フィールド内のテキストの部分一致を検索します。このクエリでは、titledescriptionnamereviewText 内で一致するものを検索します。
  • where 句: データのフィルタ条件を指定します。各セクション(映画、俳優、レビュー)では、where 句を使用して検索の特定の条件を定義します。

ウェブアプリにクエリを統合する

  1. MovieServiceapp/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 請求先アカウントにリンクされている)である必要があります。

  • Cloud 請求先アカウントには、クレジット カードなどのお支払い方法が必要です。
  • Firebase と Google Cloud を初めて使用する場合は、$300 のクレジットと無料トライアル用 Cloud 請求先アカウントを利用できるかどうかご確認ください。
  • この Codelab をイベントの一環として実施する場合は、利用可能な Cloud クレジットがあるかどうかを主催者に確認してください。

プロジェクトを Blaze プランにアップグレードする手順は次のとおりです。

  1. Firebase コンソールで、プランをアップグレードを選択します。
  2. Blaze プランを選択します。画面上の手順に沿って、Cloud 請求先アカウントをプロジェクトにリンクします。
    このアップグレードの一環として Cloud 請求先アカウントを作成する必要があった場合は、Firebase コンソールのアップグレード フローに移動してアップグレードを完了する必要があります。

ウェブアプリを Firebase プロジェクトに接続する

  1. Firebase コンソールを使用して、Firebase プロジェクトにウェブアプリを登録します。
    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. ウェブアプリをビルドする: VS Code に戻り、app フォルダで Vite を使用して、デプロイをホストするウェブアプリをビルドします。
    cd app
    npm run build
    

Firebase プロジェクトで Firebase Authentication を設定する

  1. Google ログインを使用して Firebase Authentication を設定する。62af2f225e790ef6.png
  2. (省略可)Firebase コンソールを使用して、Firebase Authentication のドメインを許可します(http://127.0.0.1 など)。
    1. [Authentication] 設定で、[Authorized Domains] に移動します。
    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 で、Movie テーブルに descriptionEmbedding フィールドを追加します。

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 に制限します。

アプリにベクトル検索機能を実装する

スキーマとクエリが設定されたので、ベクトル検索をアプリのサービスレイヤに統合します。このステップでは、ウェブアプリから検索クエリを呼び出します。

  1. app/src/lib/ MovieService.ts で、SDK からの次のインポートをコメント化解除します。これは他のクエリと同様に機能します。
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. ベクトルベースの検索をアプリに統合するには、次の関数を追加します。
    // Perform vector-based search for movies based on description
    export const searchMoviesByDescription = async (
      query: string
    ): Promise<
      | SearchMovieDescriptionUsingL2similarityData["movies_descriptionEmbedding_similarity"]
      | null
    > => {
      try {
        const response = await searchMovieDescriptionUsingL2similarity({ query });
        return response.data.movies_descriptionEmbedding_similarity;
      } catch (error) {
        console.error("Error fetching movie descriptions:", error);
        return null;
      }
    };
    

重要ポイント:

  • searchMoviesByDescription: この関数は searchMovieDescriptionUsingL2similarity クエリを呼び出し、入力テキストを渡してベクトルベースのコンテンツ検索を実行します。

実例を見る

ナビゲーション バーの [ベクトル検索] セクションに移動し、「ロマンチックでモダン」などのフレーズを入力します。検索したコンテンツに一致する映画のリストが表示されます。また、任意の映画の映画の詳細ページに移動し、ページの下部にある [類似の映画] セクションを確認することもできます。

7b71f1c75633c1be.png

12. まとめ

これで、ウェブアプリをご利用いただけるようになりました。独自の映画データで遊びたい場合は、Firebase Data Connect 拡張機能を使用して _insert.gql ファイルを模倣して独自のデータを挿入するか、VS Code の Data Connect 実行ペインから追加します。

詳細