1. 시작하기 전에
이 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 요금제로 업그레이드합니다.
2. 개발 환경 설정
이 Codelab의 이 단계에서는 Firebase Data Connect를 사용하여 영화 리뷰 앱 빌드를 시작할 수 있는 환경을 설정하는 방법을 안내합니다.
- 프로젝트 저장소를 클론하고 필요한 종속 항목을 설치합니다.
git clone https://github.com/firebaseextended/codelab-dataconnect-web cd codelab-dataconnect-web cd ./app && npm i npm run dev
- 이 명령어를 실행한 후 브라우저에서 http://localhost:5173을 열어 로컬에서 실행 중인 웹 앱을 확인합니다. 이 앱은 영화 리뷰 앱을 빌드하고 기능과 상호작용하는 프런트엔드 역할을 합니다.
- Visual Studio Code를 사용하여 클론된
codelab-dataconnect-web
폴더를 엽니다. 여기에서 스키마를 정의하고 쿼리를 작성하며 앱의 기능을 테스트합니다. - Data Connect 기능을 사용하려면 Firebase Data Connect Visual Studio 확장 프로그램을 설치합니다.
또는 Visual Studio Code Marketplace에서 확장 프로그램을 설치하거나 VS Code 내에서 검색할 수도 있습니다. - Firebase Console에서 새 Firebase 프로젝트를 열거나 만듭니다.
- Firebase 프로젝트를 Firebase Data Connect VSCode 확장 프로그램에 연결합니다. 확장 프로그램에서 다음을 수행합니다.
- 로그인 버튼을 클릭합니다.
- Firebase 프로젝트 연결을 클릭하고 Firebase 프로젝트를 선택합니다.
- Firebase Data Connect VS Code 확장 프로그램을 사용하여 Firebase 에뮬레이터를 시작합니다.
에뮬레이터 시작을 클릭한 다음 에뮬레이터가 터미널에서 실행 중인지 확인합니다.
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. 영화 리뷰의 스키마 정의
이 섹션에서는 스키마에서 영화 애플리케이션의 주요 항목 간의 구조와 관계를 정의합니다. Movie
, User
, Actor
, Review
와 같은 항목은 Firebase Data Connect 및 GraphQL 스키마 디렉티브를 사용하여 관계가 설정된 데이터베이스 테이블에 매핑됩니다. 벡터 검색을 설정하면 앱에서 인기 영화 검색, 장르별 필터링, 사용자가 리뷰를 작성하거나, 즐겨찾기를 표시하거나, 유사한 영화를 탐색하거나, 벡터 검색을 통해 텍스트 입력에 따라 추천 영화를 찾는 것까지 모든 작업을 처리할 수 있습니다.
핵심 항목 및 관계
Movie
유형은 앱에서 검색 및 영화 프로필에 사용하는 제목, 장르, 태그와 같은 주요 세부정보를 보유합니다. User
유형은 리뷰 및 즐겨찾기와 같은 사용자 상호작용을 추적합니다. Reviews
사용자를 영화에 연결하여 앱에서 사용자 제작 평점 및 의견을 표시할 수 있습니다.
영화, 배우, 사용자 간의 관계는 앱을 더욱 역동적으로 만듭니다. MovieActor
조인 테이블은 출연자 세부정보와 배우 필모그래피를 표시하는 데 도움이 됩니다. FavoriteMovie
유형을 사용하면 사용자가 영화를 즐겨찾기에 추가할 수 있으므로 앱에서 맞춤 즐겨찾기 목록을 표시하고 인기 있는 선택 항목을 강조 표시할 수 있습니다.
Movie
표 설정하기
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
유형을 참조하여 외래 키 관계를 설정합니다.
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: 영화 유형을 참조하며 외부 키 movieId: UUID!를 암시적으로 생성합니다.
- actor: Actor 유형을 참조하며 외래 키 actorId: UUID!를 암시적으로 생성합니다.
- role: 영화에서 배우의 역할을 정의합니다 (예: '기본' 또는 '지원')
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
유형은 사용자와 좋아하는 영화 간의 다대다 관계를 처리하는 조인 테이블입니다. 각 테이블은 User
를 Movie
에 연결합니다.
다음 코드 스니펫을 dataconnect/schema/schema.gql
파일에 복사하여 붙여넣습니다.
type FavoriteMovie
@table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
# @ref is implicit
user: User!
movie: Movie!
}
핵심 내용:
- movie: 영화 유형을 참조하며 외래 키
movieId: UUID!
를 암시적으로 생성합니다. - user: 사용자 유형을 참조하고 외래 키
userId: UUID!
를 암시적으로 생성합니다.
Review
표 설정하기
Review
유형은 리뷰 항목을 나타내며 다대다 관계로 User
유형과 Movie
유형을 연결합니다. 한 사용자가 여러 리뷰를 남길 수 있으며 각 영화에 여러 리뷰가 있을 수 있습니다.
다음 코드 스니펫을 dataconnect/schema/schema.gql
파일에 복사하여 붙여넣습니다.
type Review @table(name: "Reviews", key: ["movie", "user"]) {
id: UUID! @default(expr: "uuidV4()")
user: User!
movie: Movie!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
핵심 내용:
- user: 리뷰를 작성한 사용자를 참조합니다.
- movie: 리뷰 대상 영화를 참조합니다.
- reviewDate:
@default(expr: "request.time")
를 사용하여 리뷰가 생성된 시간으로 자동 설정됩니다.
자동 생성된 필드 및 기본값
스키마는 @default(expr: "uuidV4()")
와 같은 표현식을 사용하여 고유 ID와 타임스탬프를 자동으로 생성합니다. 예를 들어 Movie
및 Review
유형의 id
필드는 새 레코드가 생성될 때 UUID로 자동으로 채워집니다.
이제 스키마가 정의되었으므로 영화 앱에 데이터 구조와 관계에 대한 견고한 토대가 마련되었습니다.
5. 인기 및 최신 영화 검색
이 섹션에서는 로컬 에뮬레이터에 가상 영화 데이터를 삽입한 다음 커넥터 (쿼리)와 TypeScript 코드를 구현하여 웹 애플리케이션에서 이러한 커넥터를 호출합니다. 완료되면 앱에서 데이터베이스에서 직접 평점이 가장 높고 최신인 영화를 동적으로 가져와 표시할 수 있습니다.
예시 영화, 배우, 리뷰 데이터 삽입
- VSCode에서
dataconnect/moviedata_insert.gql
를 엽니다 . Firebase Data Connect 확장 프로그램의 에뮬레이터가 실행 중인지 확인합니다. - 파일 상단에 Run (local) 버튼이 표시됩니다. 이를 클릭하면 데이터베이스에 가상 영화 데이터가 삽입됩니다.
- Data Connect Execution 터미널을 확인하여 데이터가 추가되었는지 확인합니다.
커넥터 구현
dataconnect/movie-connector/queries.gql
를 엽니다. 주석에서 기본ListMovies
쿼리를 확인할 수 있습니다. 이 쿼리는 모든 영화와 세부정보 (예:query ListMovies @auth(level: PUBLIC) { movies { id title imageUrl releaseYear genre rating tags description } }
id
,title
,releaseYear
)를 가져옵니다. 하지만 영화를 정렬하지는 않습니다.- 기존
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 } }
- 실행 (로컬) 버튼을 클릭하여 로컬 데이터베이스에 대해 쿼리를 실행합니다. 실행하기 전에 구성 창에 쿼리 변수를 입력할 수도 있습니다.
핵심 내용:
movies()
: 데이터베이스에서 영화 데이터를 가져오는 GraphQL 쿼리 필드입니다.orderByRating
: 영화를 평점별로 정렬하는 매개변수 (오름차순/내림차순)입니다.orderByReleaseYear
: 영화를 발표 연도로 정렬하는 매개변수 (오름차순/내림차순)입니다.limit
: 반환되는 영화 수를 제한합니다.
웹 앱에 쿼리 통합하기
이 Codelab의 이 부분에서는 웹 앱에서 이전 섹션에 정의된 쿼리를 사용합니다. Firebase Data Connect 에뮬레이터는 .gql
파일 (특히 schema.gql
, queries.gql
, mutations.gql
) 및 connector.yaml
파일의 정보를 기반으로 SDK를 생성합니다. 이러한 SDK는 애플리케이션에서 직접 호출할 수 있습니다.
MovieService
(app/src/lib/MovieService.tsx
)에서 상단의 import 구문의 주석 처리를 해제합니다. 함수import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
listMovies
, 응답 유형ListMoviesData
, enumOrderDirection
는 모두 이전에 정의한 스키마 및 쿼리를 기반으로 Firebase Data Connect 에뮬레이터에서 생성한 SDK입니다 .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를 사용하여 영화 또는 배우의 세부정보를 검색하는 기능을 구현합니다. 이를 위해서는 각 테이블에서 데이터를 가져오는 것뿐만 아니라 관련 테이블을 조인하여 영화 리뷰, 배우 필모그래피와 같은 포괄적인 세부정보를 표시해야 합니다.
커넥터 구현
- 프로젝트에서
dataconnect/movie-connector/queries.gql
를 엽니다 . - 다음 쿼리를 추가하여 영화 및 배우 세부정보를 가져옵니다.
# 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 } } }
- 변경사항을 저장하고 쿼리를 검토합니다.
핵심 내용:
movie()
/actor()
:Movies
또는Actors
테이블에서 단일 영화 또는 배우를 가져오는 GraphQL 쿼리 필드입니다._on_
: 외래 키 관계가 있는 연결된 유형의 필드에 직접 액세스할 수 있습니다. 예를 들어reviews_on_movie
는 특정 영화와 관련된 모든 리뷰를 가져옵니다._via_
: 조인 테이블을 통해 다대다 관계를 탐색하는 데 사용됩니다. 예를 들어actors_via_MovieActor
는MovieActor
조인 테이블을 통해Actor
유형에 액세스하고where
조건은 역할 (예: '기본' 또는 '조연')을 기준으로 배우를 필터링합니다.
가상 데이터를 입력하여 쿼리 테스트
- 데이터 연결 실행 창에서 다음과 같은 가상 ID를 입력하여 쿼리를 테스트할 수 있습니다.
{"id": "550e8400-e29b-41d4-a716-446655440000"}
GetMovieById
의 Run (local)을 클릭하여 'Quantum Paradox' (위 ID와 관련된 모의 영화)에 관한 세부정보를 가져옵니다.
웹 앱에 쿼리 통합하기
MovieService
(app/src/lib/MovieService.tsx
)에서 다음 가져오기의 주석 처리를 해제합니다.import { getMovieById, GetMovieByIdData } from "@movie/dataconnect"; import { GetActorByIdData, getActorById } from "@movie/dataconnect";
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 인증을 사용하여 사용자 로그인 및 로그아웃 기능을 구현합니다. 또한 Firebase 인증 데이터를 사용하여 Firebase DataConnect에서 사용자 데이터를 직접 검색하거나 upsert하여 앱 내에서 안전한 사용자 관리를 보장합니다.
커넥터 구현
dataconnect/movie-connector/
에서mutations.gql
을 엽니다 .- 다음 변형을 추가하여 현재 인증된 사용자를 만들거나 업데이트합니다.
# 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
및title
를 비롯하여 사용자와 관련된 모든 리뷰를 가져옵니다.favorite_movies_on_user
: 사용자가 즐겨찾기로 표시한 모든 영화를 가져옵니다(genre
,releaseYear
,rating
,metadata
과 같은 세부정보 포함).
웹 앱에 쿼리 통합
MovieService
(app/src/lib/MovieService.tsx
)에서 다음 가져오기의 주석 처리를 삭제합니다.import { upsertUser } from "@movie/dataconnect"; import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
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 인증 에뮬레이터를 사용하여 로그인할 수 있습니다. 로그인한 후 '내 프로필'을 클릭합니다. 지금은 비어 있지만 앱에서 사용자별 데이터 처리를 위한 기반을 설정했습니다.
8. 사용자 상호작용 구현
이 Codelab의 섹션에서는 영화 리뷰 앱에서 사용자 상호작용을 구현합니다. 특히 사용자가 좋아하는 영화를 관리하고 리뷰를 작성하거나 삭제할 수 있도록 합니다.
사용자가 영화를 즐겨찾기에 추가하도록 허용하기
이 섹션에서는 사용자가 영화를 즐겨찾기에 추가할 수 있도록 데이터베이스를 설정합니다.
커넥터 구현
dataconnect/movie-connector/
에서mutations.gql
을 엽니다 .- 영화에 즐겨찾기를 추가하는 다음 변형을 추가합니다.
# 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
를 사용하여 인증된 사용자의 데이터에만 액세스하거나 수정합니다.
영화가 즐겨찾기에 추가되어 있는지 확인하기
dataconnect/movie-connector/
에서queries.gql
을 엽니다 .- 영화가 즐겨찾기에 추가되어 있는지 확인하는 다음 쿼리를 추가합니다.
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) { movieId } }
핵심 내용:
auth.uid
: Firebase 인증을 사용하여 사용자별 데이터에 대한 보안 액세스를 보장합니다.favorite_movie
:favorite_movies
조인 테이블을 확인하여 특정 영화가 현재 사용자에 의해 즐겨찾기로 표시되었는지 확인합니다.
웹 앱에 쿼리 통합하기
MovieService
(app/src/lib/MovieService.tsx
)에서 다음 가져오기의 주석 처리를 해제합니다.import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
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; } };
핵심 내용:
handleAddFavoritedMovie
및handleDeleteFavoritedMovie
: 이 변형을 사용하여 사용자의 즐겨찾기에서 영화를 안전하게 추가하거나 삭제합니다.handleGetIfFavoritedMovie
:getIfFavoritedMovie
쿼리를 사용하여 사용자가 영화를 즐겨찾기로 표시했는지 확인합니다.
실제 사례 보기
이제 영화 카드와 영화 세부정보 페이지에서 하트 아이콘을 클릭하여 영화를 즐겨찾기에 추가하거나 즐겨찾기에서 삭제할 수 있습니다. 또한 프로필 페이지에서 좋아하는 영화를 볼 수 있습니다.
사용자가 리뷰를 작성하거나 삭제하도록 허용하기
다음으로 앱에서 사용자 리뷰를 관리하는 섹션을 구현합니다.
커넥터 구현
mutations.gql
(dataconnect/movie-connector/mutations.gql
): 다음 변이를 추가합니다.
# Add a review for a movie
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
@auth(level: USER) {
review_insert(
data: {
userId_expr: "auth.uid"
movieId: $movieId
rating: $rating
reviewText: $reviewText
reviewDate_date: { today: true }
}
)
}
# Delete a user's review for a movie
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
핵심 내용:
userId_expr: "auth.uid"
: 리뷰가 인증된 사용자와 연결되도록 합니다.reviewDate_date: { today: true }
: DataConnect를 사용하여 검토의 현재 날짜를 자동으로 생성하므로 수동으로 입력할 필요가 없습니다.
웹 앱에 쿼리 통합하기
MovieService
(app/src/lib/MovieService.tsx
)에서 다음 가져오기의 주석 처리를 해제합니다.import { addReview, deleteReview } from "@movie/dataconnect";
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. 고급 필터 및 부분 텍스트 일치
이 섹션에서는 사용자가 다양한 평점 및 출시 연도를 기준으로 영화를 검색하고, 장르 및 태그로 필터링하고, 제목이나 설명에서 부분 텍스트 일치를 실행하고, 여러 필터를 결합하여 더 정확한 결과를 얻을 수 있는 고급 검색 기능을 구현합니다.
커넥터 구현
dataconnect/movie-connector/
에서queries.gql
를 엽니다.- 다양한 검색 기능을 지원하도록 다음 쿼리를 추가합니다.
# 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
절을 사용하여 검색의 구체적인 기준을 정의합니다.
웹 앱에 쿼리 통합하기
MovieService
(app/src/lib/MovieService.tsx
)에서 다음 가져오기의 주석 처리를 해제합니다.import { searchAll, SearchAllData } from "@movie/dataconnect";
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를 PostgreSQL용 Cloud SQL과 통합하려면 Firebase 프로젝트가 사용한 만큼만 지불 (Blaze) 요금제를 사용해야 합니다. 즉, Cloud Billing 계정에 연결되어 있어야 합니다.
- Cloud Billing 계정에는 신용카드와 같은 결제 수단이 필요합니다.
- Firebase와 Google Cloud를 처음 사용하는 경우 $300 크레딧과 무료 체험판 Cloud Billing 계정을 받을 자격이 되는지 확인하세요.
- 이벤트의 일환으로 이 Codelab을 진행하는 경우 주최자에게 사용 가능한 Cloud 크레딧이 있는지 문의하세요.
프로젝트를 Blaze 요금제로 업그레이드하려면 다음 단계를 따르세요.
- Firebase Console에서 요금제를 업그레이드하도록 선택합니다.
- Blaze 요금제를 선택합니다. 화면에 표시된 안내에 따라 Cloud Billing 계정을 프로젝트에 연결합니다.
이 업그레이드의 일환으로 Cloud Billing 계정을 만들어야 하는 경우 업그레이드를 완료하기 위해 Firebase Console의 업그레이드 흐름으로 돌아가야 할 수 있습니다.
웹 앱을 Firebase 프로젝트에 연결하기
- Firebase Console을 사용하여 Firebase 프로젝트에 웹 앱을 등록합니다.
- 프로젝트를 연 다음 앱 추가를 클릭합니다.
- 지금은 SDK 설정 및 구성 설정을 무시하되 생성된
firebaseConfig
객체는 복사해야 합니다.
app/src/lib/firebase.tsx
의 기존firebaseConfig
를 방금 Firebase Console에서 복사한 구성으로 바꿉니다.const firebaseConfig = { apiKey: "API_KEY", authDomain: "PROJECT_ID.firebaseapp.com", projectId: "PROJECT_ID", storageBucket: "PROJECT_ID.firebasestorage.app", messagingSenderId: "SENDER_ID", appId: "APP_ID" };
- 웹 앱 빌드: VS Code의
app
폴더로 돌아가서 Vite를 사용하여 호스팅 배포를 위한 웹 앱을 빌드합니다.cd app npm run build
Firebase 프로젝트에서 Firebase 인증 설정하기
- Google 로그인을 사용하여 Firebase 인증을 설정합니다.
- (선택사항) Firebase Console을 사용하여 Firebase 인증의 도메인을 허용합니다 (예:
http://127.0.0.1
).- 인증 설정에서 승인된 도메인으로 이동합니다.
- '도메인 추가'를 클릭하고 목록에 로컬 도메인을 포함합니다.
Firebase CLI를 사용하여 배포
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"]
- 프로젝트에 Firebase CLI가 설정되어 있는지 확인합니다.
npm i -g firebase-tools firebase login --reauth firebase use --add
- 터미널에서 다음 명령어를 실행하여 배포합니다.
firebase deploy --only dataconnect,hosting
- 다음 명령어를 실행하여 스키마 변경사항을 비교합니다.
firebase dataconnect:sql:diff
- 변경사항이 허용되는 경우 다음을 사용하여 적용합니다.
firebase dataconnect:sql:migrate
PostgreSQL용 Cloud SQL 인스턴스가 최종적으로 배포된 스키마와 데이터로 업데이트됩니다. Firebase Console에서 상태를 모니터링할 수 있습니다.
이제 your-project.web.app/
에서 앱을 실시간으로 볼 수 있습니다. 또한 로컬 에뮬레이터에서와 마찬가지로 Firebase Data Connect 패널에서 Run (Production)을 클릭하여 프로덕션 환경에 데이터를 추가할 수 있습니다.
11. 선택사항: Firebase Data Connect를 사용한 벡터 검색(결제 필요)
이 섹션에서는 Firebase Data Connect를 사용하여 영화 리뷰 앱에서 벡터 검색을 사용 설정합니다. 이 기능을 사용하면 벡터 임베딩을 사용하여 설명이 유사한 영화를 찾는 등 콘텐츠 기반 검색을 할 수 있습니다.
이 단계를 수행하려면 Google Cloud에 배포하기 위한 이 Codelab의 마지막 단계를 완료해야 합니다.
필드의 임베딩을 포함하도록 스키마 업데이트
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 활성화
- 기본 요건 가이드에 따라 Google Cloud에서 Vertex AI API를 설정합니다. 이 단계는 임베딩 생성 및 벡터 검색 기능을 지원하는 데 필수적입니다.
- Firebase Data Connect VS Code 확장 프로그램을 사용하여 '프로덕션에 배포'를 클릭하여
pgvector
및 벡터 검색을 활성화하도록 스키마를 다시 배포합니다.
데이터베이스에 임베딩 채우기
- VS Code에서
dataconnect
폴더를 엽니다. optional_vector_embed.gql
에서 Run(local)(실행(로컬))을 클릭하여 데이터베이스에 영화의 임베딩을 채웁니다.
벡터 검색 쿼리 추가
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개로 제한합니다.
앱에 벡터 검색 함수 구현
이제 스키마와 쿼리가 설정되었으므로 벡터 검색을 앱의 서비스 레이어에 통합합니다. 이 단계를 통해 웹 앱에서 검색어를 호출할 수 있습니다.
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
쿼리를 호출하여 입력 텍스트를 전달하여 벡터 기반 콘텐츠 검색을 실행합니다.
실제 사례 보기
탐색 메뉴에서 '벡터 검색' 섹션으로 이동하여 '로맨틱하고 현대적'과 같은 문구를 입력합니다. 검색하는 콘텐츠와 일치하는 영화 목록이 표시되거나 영화의 영화 세부정보 페이지로 이동하여 페이지 하단의 유사한 영화 섹션을 확인할 수 있습니다.
12. 결론
축하합니다. 웹 앱을 사용할 수 있습니다. 자체 영화 데이터를 사용해 놀고 싶다면 걱정하지 마세요. _insert.gql
파일을 모방하여 Firebase Data Connect 확장 프로그램을 사용하여 자체 데이터를 삽입하거나 VS Code의 Data Connect 실행 창을 통해 추가하면 됩니다.