Firebase Data Connect로 빌드

1. 시작하기 전에

FriendsMovies 앱

이 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 Console에서 프로젝트를 만듭니다.
  • (선택사항) 벡터 검색의 경우 프로젝트를 Blaze 요금제로 업그레이드합니다.

개발 환경 설정

이 섹션에서는 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 Console로 이동하여 새 Firebase 프로젝트를 만듭니다. 그런 다음 Firebase Data Connect VSCode 확장 프로그램에서 다음을 실행합니다.

  • 로그인 버튼을 클릭합니다.
  • Firebase 프로젝트 연결을 클릭하고 Firebase Console에서 만든 프로젝트를 선택합니다.

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 생성을 위한 구성 파일

앱/src/

애플리케이션 로직과 Firebase Data Connect와의 상호작용이 포함됩니다.

  • firebase.ts: Console에서 Firebase 앱에 연결하기 위한 구성입니다.
  • lib/dataconnect-sdk/: 생성된 SDK가 포함된 폴더입니다. connector/connector.yaml 파일에서 SDK 생성 위치를 수정할 수 있으며 쿼리 또는 변형을 정의할 때마다 SDK가 자동으로 생성됩니다.

3. 영화 리뷰의 스키마 정의

이 섹션에서는 스키마에서 영화 애플리케이션의 주요 항목 간의 구조와 관계를 정의합니다. Movie, User, Actor, Review와 같은 항목은 Firebase Data Connect 및 GraphQL 스키마 지시문을 사용하여 설정된 관계를 사용하여 데이터베이스 테이블에 매핑됩니다. 앱이 준비되면 최고 평점 영화 검색, 장르별 필터링, 사용자가 리뷰를 남기거나, 즐겨찾기를 표시하거나, 유사한 영화를 탐색하거나, 벡터 검색을 통해 텍스트 입력을 기반으로 추천 영화를 찾을 수 있는 등 모든 작업을 처리할 준비가 될 것입니다.

핵심 항목 및 관계

Movie 유형은 앱에서 검색 및 영화 프로필에 사용하는 제목, 장르, 태그와 같은 주요 세부정보를 보유합니다. User 유형은 리뷰 및 즐겨찾기와 같은 사용자 상호작용을 추적합니다. Reviews는 사용자를 영화에 연결해 주므로 앱에서 사용자가 생성한 평점과 의견을 표시할 수 있습니다.

영화, 배우, 사용자 간의 관계는 앱을 더욱 동적으로 만듭니다. MovieActor 조인 테이블은 출연자 세부정보와 배우 필모그래피를 표시하는 데 도움이 됩니다. FavoriteMovie 유형을 사용하면 사용자가 영화를 즐겨찾기에 추가할 수 있으므로 앱에서 맞춤 즐겨찾기 목록을 표시하고 인기 있는 선택 항목을 강조 표시할 수 있습니다.

영화 표

Movie 유형은 title, genre, releaseYear, rating과 같은 필드를 포함하여 영화 항목의 기본 구조를 정의합니다.

다음 코드 스니펫을 dataconnect/schema/schema.gql 파일에 복사하여 붙여넣습니다.

type Movie
  @table {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
}

핵심 내용:

  • id: @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 유형을 참조하여 외래 키 관계를 설정합니다.

작업 수행자 표

코드 스니펫을 복사하여 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: 영화 유형을 참조하며 외부 키 movieId: UUID!를 암시적으로 생성합니다.
  • 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 유형은 사용자와 좋아하는 영화나 배우 간의 다대다 관계를 처리하는 조인 테이블입니다. 각 테이블은 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: 영화 유형을 참조하며 외부 키 movieId: UUID!를 암시적으로 생성합니다.
  • user: 사용자 유형을 참조하고 외부 키 userId: UUID!를 암시적으로 생성합니다.

검토 표

리뷰 유형은 리뷰 항목을 나타내며 다대다 관계로 사용자 및 영화 유형을 연결합니다. 한 사용자는 여러 리뷰를 남길 수 있고 각 영화에는 여러 개의 리뷰가 있을 수 있습니다.

다음 코드 스니펫을 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와 타임스탬프를 자동으로 생성합니다. 예를 들어 영화 및 리뷰 유형의 ID 필드는 새 레코드가 생성될 때 UUID로 자동 채워집니다.

이제 스키마가 정의되었으므로 영화 앱에 데이터 구조와 관계에 대한 견고한 토대가 마련되었습니다.

4. 인기 및 최신 영화 검색

FriendsMovies 앱

이 섹션에서는 로컬 에뮬레이터에 가상 영화 데이터를 삽입한 다음 커넥터(쿼리)와 TypeScript 코드를 구현하여 웹 애플리케이션에서 이러한 커넥터를 호출합니다. 완료되면 앱에서 데이터베이스에서 직접 평점이 가장 높고 최신인 영화를 동적으로 가져와 표시할 수 있습니다.

예시 영화, 배우, 리뷰 데이터 삽입

  1. VSCode에서 dataconnect/moviedata_insert.gql엽니다 . Firebase Data Connect 확장 프로그램의 에뮬레이터가 실행 중인지 확인합니다.
  2. 파일 상단에 Run (local) 버튼이 표시됩니다. 이 버튼을 클릭하면 모의 영화 데이터가 데이터베이스에 삽입됩니다.

e424f75e63bf2e10.png

  1. 데이터 연결 실행 터미널을 확인하여 데이터가 성공적으로 추가되었는지 확인합니다.

e0943d7704fb84ea.png

커넥터 구현

  1. dataconnect/movie-connector/queries.gql를 엽니다. 주석에서 기본적인 ListMovies 쿼리를 확인할 수 있습니다.
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

이 쿼리는 모든 영화와 영화의 세부정보 (예: id, title, releaseYear)를 가져옵니다. 하지만 영화를 정렬하지는 않습니다.

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

실행 (로컬) 버튼을 클릭하여 로컬 데이터베이스에 대해 쿼리를 실행합니다. 실행하기 전에 구성 창에 쿼리 변수를 입력할 수도 있습니다.

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 구문의 주석 처리를 해제합니다.
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

listMovies 함수, 응답 유형 ListMoviesData, enum 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(): Movies 또는 Actors 테이블에서 단일 영화 또는 배우를 가져오기 위한 GraphQL 쿼리 필드입니다.
  • _on_: 외래 키 관계가 있는 연결된 유형의 필드에 직접 액세스할 수 있습니다. 예를 들어 reviews_on_movie는 특정 영화와 관련된 모든 리뷰를 가져옵니다.
  • _via_: 조인 테이블을 통해 다대다 관계를 탐색하는 데 사용됩니다. 예를 들어 actors_via_MovieActor는 MovieActor 조인 테이블을 통해 배우 유형에 액세스하고 where 조건은 역할(예: '기본' 또는 '지원').

Data Connect 실행 창에서 다음과 같은 모의 ID를 입력하여 쿼리를 테스트할 수 있습니다.

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

GetMovieByIdRun (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에서 사용자 데이터를 직접 검색하거나 upsert하여 앱 내에서 안전한 사용자 관리를 보장합니다.

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": 사용자나 앱이 아닌 Firebase 인증에서 직접 제공하는 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 Auth 에뮬레이터를 사용하여 로그인할 수 있습니다. 로그인한 후 '내 프로필'을 클릭합니다. 지금은 비어 있지만 앱에서 사용자별 데이터 처리를 위한 기반을 설정했습니다.

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": Firebase 인증에서 직접 제공하는 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 인증을 사용하여 사용자별 데이터에 안전하게 액세스할 수 있도록 합니다.
  • favorite_movie: favorite_movies 조인 테이블을 확인하여 특정 영화가 현재 사용자에 의해 즐겨찾기로 표시되었는지 확인합니다.

웹 앱에 쿼리 통합

  1. MovieService(app/src/lib/MovieService.tsx)에서 다음 가져오기의 주석 처리를 해제합니다.
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. handleAddFavoritedMovie, handleDeleteFavoritedMovie, handleGetIfFavoritedMovie 함수를 다음 코드로 바꿉니다.
// Add a movie to user's favorites
export const handleAddFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await addFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error adding movie to favorites:", error);
    throw error;
  }
};

// Remove a movie from user's favorites
export const handleDeleteFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await deleteFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error removing movie from favorites:", error);
    throw error;
  }
};

// Check if the movie is favorited by the user
export const handleGetIfFavoritedMovie = async (
  movieId: string
): Promise<boolean> => {
  try {
    const response = await getIfFavoritedMovie({ movieId });
    return !!response.data.favorite_movie;
  } catch (error) {
    console.error("Error checking if movie is favorited:", error);
    return false;
  }
};

핵심 요약:

  • 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 연산자: 단일 쿼리에서 여러 조건을 결합하여 releaseYear, rating, genre와 같은 여러 필드로 검색을 필터링할 수 있습니다.
  • contains 연산자: 필드 내에서 부분 텍스트 일치를 검색합니다. 이 쿼리에서는 title, description, name 또는 reviewText 내에서 일치하는 항목을 찾습니다.
  • 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 Console에서 웹 앱 추가

  1. Firebase Console에서 웹 앱을 만들고 앱 ID를 기록합니다.

7030822793e4d75b.png

  1. Firebase Console에서 '앱 추가'를 클릭하여 웹 앱을 설정합니다. 지금은 SDK 설정 및 구성 설정을 무시해도 되지만 생성된 firebaseConfig 객체는 알아두세요.
  2. app/src/lib/firebase.tsxfirebaseConfig바꿉니다.
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

Console에서 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

PostgreSQL용 Cloud SQL 인스턴스가 최종 배포된 스키마 및 데이터로 업데이트됩니다. Firebase Console에서 상태를 모니터링할 수 있습니다.

이제 your-project.web.app/에서 앱을 실시간으로 볼 수 있습니다. 또한 로컬 에뮬레이터에서와 마찬가지로 Firebase Data Connect 패널에서 Run (Production)을 클릭하여 프로덕션 환경에 데이터를 추가할 수 있습니다.

10. 선택사항: Firebase Data Connect를 사용한 벡터 검색

이 섹션에서는 Firebase Data Connect를 사용하여 영화 리뷰 앱에서 벡터 검색을 사용 설정합니다. 이 기능을 사용하면 벡터 임베딩을 사용하여 설명이 유사한 영화를 찾는 등 콘텐츠 기반 검색을 할 수 있습니다.

4b5aca5a447d2feb.png

필드의 임베딩을 포함하도록 스키마 업데이트

  1. 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 VSCode 확장 프로그램을 사용하여 '프로덕션에 배포'를 클릭하여 pgvector 및 벡터 검색을 활성화하도록 스키마를 다시 배포합니다.

임베딩으로 데이터베이스 채우기

  1. VSCode에서 dataconnect 폴더를 열고 optional_vector_embed.gql에서 Run(local)을 클릭하여 데이터베이스에 영화의 임베딩을 채웁니다.

b858da780f6ec103.png

벡터 검색어 추가

  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 확장 프로그램을 사용하여 자체 데이터를 삽입하거나 데이터 연결 실행 창을 통해 추가하면 됩니다.

자세히 알아보기