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 プロジェクトを開くか、新しいプロジェクトを作成します。
  6. Firebase プロジェクトを Firebase Data Connect VSCode 拡張機能に接続します。拡張機能で次の操作を行います。
    1. [ログイン] ボタンをクリックします。
    2. [Connect a Firebase Project] をクリックして、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: @default(expr: "uuidV4()") を使用して生成された、映画ごとに一意の UUID。

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 テーブルを設定する

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 型は、ユーザーとお気に入りの映画の間の多対多の関係を処理する結合テーブルです。各テーブルは 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: 返される映画の数を制限します。

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

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

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

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

1b08961891e44da2.png

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

  1. MovieServiceapp/src/lib/MovieService.tsx)で、次の import のコメントを解除します。
    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/Open 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": ユーザーまたはアプリではなく、Firebase Authentication から直接提供される auth.uid を使用します。ユーザー ID が安全かつ自動的に処理されるようにすることで、セキュリティ レイヤを追加します。

現在のユーザーを取得する

  1. dataconnect/movie-connector/Open 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 のコメントを解除します。
    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/Open 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/Open 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 のコメントを解除します。
    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 のコメントを解除します。
    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 のコメントを解除します。
    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. [認証] 設定で、[認可済みドメイン] に移動します。
    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 パネルで [Run (Production)] をクリックして、本番環境にデータを追加することもできます。

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 拡張機能を使用して [Deploy to Production] をクリックし、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. まとめ

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

詳細