Criar com o Firebase Data Connect (Web)

1. Antes de começar

App FriendlyMovies

Neste codelab, você vai integrar o Firebase Data Connect a um banco de dados do Cloud SQL para criar um app da Web de avaliação de filmes. O app concluído mostra como o Firebase Data Connect simplifica o processo de criação de aplicativos com tecnologia SQL. Ele inclui estes recursos:

  • Autenticação:implemente a autenticação personalizada para consultas e mutações do app, garantindo que apenas usuários autorizados possam interagir com seus dados.
  • Esquema do GraphQL:crie e gerencie suas estruturas de dados usando um esquema flexível do GraphQL adaptado às necessidades de um web app de avaliação de filmes.
  • Consultas e mutações SQL:recupere, atualize e gerencie dados no Cloud SQL usando consultas e mutações com tecnologia do GraphQL.
  • Pesquisa avançada com correspondência parcial de string:use filtros e opções de pesquisa para encontrar filmes com base em campos como título, descrição ou tags.
  • (Opcional) Integração da pesquisa vetorial:adicione a funcionalidade de pesquisa de conteúdo usando a pesquisa vetorial do Firebase Data Connect para oferecer uma experiência do usuário avançada com base em entradas e preferências.

Pré-requisitos

É necessário ter um conhecimento básico de JavaScript.

O que você aprenderá

  • Configure o Firebase Data Connect com emuladores locais.
  • Projete um esquema de dados usando o Data Connect e o GraphQL.
  • Escrever e testar várias consultas e mutações para um app de avaliação de filmes.
  • Saiba como o Firebase Data Connect gera e usa o SDK no app.
  • Implante seu esquema e gerencie o banco de dados com eficiência.

O que é necessário

2. Configurar o ambiente de desenvolvimento

Esta etapa do codelab vai orientar você na configuração do ambiente para começar a criar seu app de avaliação de filmes usando o Firebase Data Connect.

  1. Clone o repositório do projeto e instale as dependências necessárias:
    git clone https://github.com/firebaseextended/codelab-dataconnect-web
    cd codelab-dataconnect-web
    cd ./app && npm i
    npm run dev
    
  2. Depois de executar esses comandos, abra http://localhost:5173 no navegador para ver o web app em execução local. Ele serve como front-end para criar o app de avaliação de filmes e interagir com os recursos dele.93f6648a2532c606.png
  3. Abra a pasta codelab-dataconnect-web clonada usando o Visual Studio Code. É aqui que você vai definir o esquema, escrever consultas e testar a funcionalidade do app.
  4. Para usar os recursos do Data Connect, instale a extensão do Firebase Data Connect para Visual Studio.
    Também é possível instalar a extensão no Visual Studio Code Marketplace ou pesquisar por ela no VS Code.b03ee38c9a81b648.png
  5. Abra ou crie um projeto do Firebase no console do Firebase.
  6. Conecte seu projeto do Firebase à extensão do Firebase Data Connect no VSCode. Na extensão, faça o seguinte:
    1. Clique no botão Fazer login.
    2. Clique em Conectar um projeto do Firebase e selecione seu projeto.
    4bb2fbf8f9fac29b.png
  7. Inicie os emuladores do Firebase usando a extensão do Firebase Data Connect para VS Code:
    clique em Iniciar emuladores e confirme se eles estão em execução no terminal.6d3d95f4cb708db1.png

3. Revisar a base de código inicial

Nesta seção, você vai conhecer as principais áreas da base de código inicial do app. Embora o app não tenha algumas funcionalidades, é útil entender a estrutura geral.

Estrutura de pastas e arquivos

As subseções a seguir oferecem uma visão geral da estrutura de pastas e arquivos do app.

O diretório dataconnect/

Contém configurações, conectores (que definem consultas e mutações) e arquivos de esquema do Firebase Data Connect.

  • schema/schema.gql: define o esquema do GraphQL.
  • connector/queries.gql: consultas necessárias no seu app
  • connector/mutations.gql: mutações necessárias no seu app
  • connector/connector.yaml: arquivo de configuração para geração do SDK

O diretório app/src/

Contém a lógica do aplicativo e a interação com o Firebase Data Connect.

  • firebase.ts: configuração para se conectar a um app do Firebase no seu projeto do Firebase.
  • lib/dataconnect-sdk/: contém o SDK gerado. É possível editar o local da geração do SDK no arquivo connector/connector.yaml, e os SDKs serão gerados automaticamente sempre que você definir uma consulta ou mutação.

4. Definir um esquema para avaliações de filmes

Nesta seção, você vai definir a estrutura e as relações entre as principais entidades do aplicativo de filmes em um esquema. Entidades como Movie, User, Actor e Review são mapeadas para tabelas de banco de dados, com relacionamentos estabelecidos usando o Firebase Data Connect e diretivas de esquema GraphQL. Depois de implementado, o app estará pronto para lidar com tudo, desde a pesquisa de filmes mais bem avaliados e a filtragem por gênero até permitir que os usuários deixem avaliações, marquem favoritos, explorem filmes semelhantes ou encontrem filmes recomendados com base na entrada de texto por pesquisa vetorial.

Entidades e relacionamentos principais

O tipo Movie contém detalhes importantes, como título, gênero e tags, que o app usa para pesquisas e perfis de filmes. O tipo User rastreia interações do usuário, como avaliações e favoritos. Reviews conectar usuários a filmes, permitindo que o app mostre classificações e feedback gerados pelos usuários.

As relações entre filmes, atores e usuários tornam o app mais dinâmico. A tabela de junção MovieActor ajuda a mostrar detalhes do elenco e filmografias de atores. O tipo FavoriteMovie permite que os usuários adicionem filmes aos favoritos. Assim, o app pode mostrar uma lista personalizada de favoritos e destacar as opções mais procuradas.

Configurar a tabela Movie

O tipo Movie define a estrutura principal de uma entidade de filme, incluindo campos como title, genre, releaseYear e rating.

Copie e cole o snippet de código no arquivo 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]
}

Principais conclusões:

  • id:um UUID exclusivo para cada filme, gerado usando @default(expr: "uuidV4()").

Configurar a tabela MovieMetadata

O tipo MovieMetadata estabelece uma relação de um para um com o tipo Movie. Ele inclui outros dados, como o diretor do filme.

Copie e cole o snippet de código no arquivo 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
}

Principais conclusões:

  • Filme! @ref: faz referência ao tipo Movie, estabelecendo uma relação de chave estrangeira.

Configurar a tabela Actor

Copie e cole o snippet de código no arquivo dataconnect/schema/schema.gql:

type Actor @table {
  id: UUID!
  imageUrl: String!
  name: String! @col(name: "name", dataType: "varchar(30)")
}

O tipo Actor representa um ator no banco de dados de filmes, em que cada ator pode fazer parte de vários filmes, formando uma relação de muitos para muitos.

Configurar a tabela MovieActor

Copie e cole o snippet de código no arquivo 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"
}

Principais conclusões:

  • movie:faz referência ao tipo "Movie" e gera implicitamente uma chave estrangeira "movieId: UUID!".
  • actor:faz referência ao tipo Actor e gera implicitamente uma chave externa actorId: UUID!.
  • role:define a função do ator no filme (por exemplo, "principal" ou "secundário").

Configurar a tabela User

O tipo User define uma entidade de usuário que interage com filmes deixando avaliações ou marcando filmes como favoritos.

Copie e cole o snippet de código no arquivo 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
}

Configurar a tabela FavoriteMovie

O tipo FavoriteMovie é uma tabela de junção que processa relações de muitos para muitos entre usuários e seus filmes favoritos. Cada tabela vincula um User a um Movie.

Copie e cole o snippet de código no arquivo 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!
}

Principais conclusões:

  • movie:faz referência ao tipo "Movie" e gera implicitamente uma chave estrangeira movieId: UUID!.
  • user:faz referência ao tipo de usuário e gera implicitamente uma chave estrangeira userId: UUID!.

Configurar a tabela Review

O tipo Review representa a entidade de avaliação e vincula os tipos User e Movie em um relacionamento de muitos para muitos (um usuário pode deixar muitas avaliações, e cada filme pode ter muitas avaliações).

Copie e cole o snippet de código no arquivo 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")
}

Principais conclusões:

  • user:faz referência ao usuário que deixou a avaliação.
  • movie:faz referência ao filme que está sendo avaliado.
  • reviewDate:definido automaticamente para o momento em que a avaliação é criada usando @default(expr: "request.time").

Campos e padrões gerados automaticamente

O esquema usa expressões como @default(expr: "uuidV4()") para gerar automaticamente IDs e carimbos de data/hora exclusivos. Por exemplo, o campo id nos tipos Movie e Review é preenchido automaticamente com um UUID quando um novo registro é criado.

Agora que o esquema está definido, o app de filmes tem uma base sólida para a estrutura e os relacionamentos de dados.

5. Recuperar os melhores e mais recentes filmes

App FriendlyMovies

Nesta seção, você vai inserir dados de filmes simulados nos emuladores locais e implementar os conectores (consultas) e o código TypeScript para chamar esses conectores no aplicativo da Web. No final, seu app poderá buscar e mostrar dinamicamente os filmes mais recentes e com melhor classificação diretamente do banco de dados.

Inserir dados simulados de filmes, atores e avaliações

  1. No VSCode, abra dataconnect/moviedata_insert.gql. Verifique se os emuladores na extensão do Firebase Data Connect estão em execução.
  2. Você vai encontrar um botão Executar (local) na parte de cima do arquivo. Clique aqui para inserir os dados simulados de filmes no banco de dados.
    e424f75e63bf2e10.png
  3. Verifique o terminal Execução do Data Connect para confirmar se os dados foram adicionados corretamente.
    e0943d7704fb84ea.png

Implementar o conector

  1. Abra dataconnect/movie-connector/queries.gql. Você vai encontrar uma consulta básica do ListMovies nos comentários:
    query ListMovies @auth(level: PUBLIC) {
      movies {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
    Essa consulta busca todos os filmes e os detalhes deles (por exemplo, id, title, releaseYear). No entanto, ela não classifica os filmes.
  2. Substitua a consulta ListMovies atual pela seguinte para adicionar opções de classificação e limite:
    # 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. Clique no botão Executar (local) para executar a consulta no banco de dados local. Também é possível inserir as variáveis de consulta no painel de configuração antes de executar.
    c4d947115bb11b16.png

Principais conclusões:

  • movies():campo de consulta GraphQL para buscar dados de filmes do banco de dados.
  • orderByRating:parâmetro para classificar filmes por avaliação (crescente/decrescente).
  • orderByReleaseYear:parâmetro para classificar filmes por ano de lançamento (crescente/decrescente).
  • limit:restringe o número de filmes retornados.

Integrar consultas no web app

Nesta parte do codelab, você vai usar as consultas definidas na seção anterior no seu app da Web. Os emuladores do Firebase Data Connect geram SDKs com base nas informações dos arquivos .gql (especificamente, schema.gql, queries.gql, mutations.gql) e do arquivo connector.yaml. Esses SDKs podem ser chamados diretamente no seu aplicativo.

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário da declaração de importação na parte de cima:
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    A função listMovies, o tipo de resposta ListMoviesData e a enumeração OrderDirection são todos SDKs gerados pelos emuladores do Firebase Data Connect com base no esquema e nas consultas que você definiu anteriormente .
  2. Substitua as funções handleGetTopMovies e handleGetLatestMovies pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • listMovies:uma função gerada automaticamente que chama a consulta listMovies para recuperar uma lista de filmes. Ele inclui opções para classificar por classificação ou ano de lançamento e limitar o número de resultados.
  • ListMoviesData:o tipo de resultado usado para mostrar os 10 melhores e os filmes mais recentes na página inicial do app.

Veja isso na prática

Atualize o web app para ver a consulta em ação. A página inicial agora mostra dinamicamente a lista de filmes, buscando dados diretamente do banco de dados local. Os filmes mais recentes e com as melhores avaliações vão aparecer sem problemas, refletindo os dados que você acabou de configurar.

6. Mostrar detalhes de filmes e atores

Nesta seção, você vai implementar a funcionalidade para recuperar informações detalhadas de um filme ou ator usando os IDs exclusivos deles. Isso envolve não apenas buscar dados das respectivas tabelas, mas também unir tabelas relacionadas para mostrar detalhes abrangentes, como críticas de filmes e filmografias de atores.

ac7fefa7ff779231.png

Implementar conectores

  1. Abra dataconnect/movie-connector/queries.gql no seu projeto.
  2. Adicione as seguintes consultas para recuperar detalhes de filmes e atores:
    # 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. Salve as mudanças e revise as consultas.

Principais conclusões:

  • movie()/actor(): campos de consulta GraphQL para buscar um único filme ou ator na tabela Movies ou Actors.
  • _on_: permite acesso direto a campos de um tipo associado que tem uma relação de chave estrangeira. Por exemplo, reviews_on_movie busca todas as avaliações relacionadas a um filme específico.
  • _via_: usado para navegar em relacionamentos de muitos para muitos por uma tabela de junção. Por exemplo, actors_via_MovieActor acessa o tipo Actor pela tabela de junção MovieActor, e a condição where filtra os atores com base na função deles (por exemplo, "principal" ou "secundário").

Testar a consulta inserindo dados simulados

  1. No painel de execução do Data Connect, é possível testar a consulta inserindo IDs simulados, como:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Clique em Executar (local) para GetMovieById e recupere os detalhes sobre "Paradoxo quântico", o filme simulado a que o ID acima se refere.

1b08961891e44da2.png

Integrar consultas no web app

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário das seguintes importações:
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. Substitua as funções handleGetMovieById e handleGetActorById pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • getMovieById / getActorById: são funções geradas automaticamente que chamam as consultas definidas, recuperando informações detalhadas sobre um filme ou ator específico.
  • GetMovieByIdData / GetActorByIdData: são os tipos de resultados usados para mostrar detalhes de filmes e atores no app.

Veja isso na prática

Agora, acesse a página inicial do seu app da Web. Clique em um filme para ver todos os detalhes, incluindo atores e avaliações, informações extraídas de tabelas relacionadas. Da mesma forma, clicar em um ator vai mostrar os filmes em que ele participou.

7. Processar a autenticação do usuário

Nesta seção, você vai implementar a funcionalidade de login e logout do usuário usando o Firebase Authentication. Você também vai usar os dados do Firebase Authentication para recuperar ou inserir dados do usuário diretamente no Firebase DataConnect, garantindo o gerenciamento seguro de usuários no seu app.

9890838045d5a00e.png

Implementar conectores

  1. Abra mutations.gql no dataconnect/movie-connector/.
  2. Adicione a seguinte mutação para criar ou atualizar o usuário autenticado atual:
    # Create or update the current authenticated user
    mutation UpsertUser($username: String!) @auth(level: USER) {
      user_upsert(
        data: {
          id_expr: "auth.uid"
          username: $username
        }
      )
    }
    

Principais conclusões:

  • id_expr: "auth.uid": usa auth.uid, que é fornecido diretamente pelo Firebase Authentication, não pelo usuário ou pelo app, adicionando uma camada extra de segurança ao garantir que o ID do usuário seja processado de forma segura e automática.

Buscar o usuário atual

  1. Abra queries.gql no dataconnect/movie-connector/.
  2. Adicione a seguinte consulta para buscar o usuário atual:
    # 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
            }
          }
        }
      }
    }
    

Principais conclusões:

  • auth.uid: recuperado diretamente do Firebase Authentication, garantindo acesso seguro a dados específicos do usuário.
  • Campos _on_: representam as tabelas de junção:
    • reviews_on_user: busca todas as avaliações relacionadas ao usuário, incluindo o id e o title do filme.
    • favorite_movies_on_user: recupera todos os filmes marcados como favoritos pelo usuário, incluindo informações detalhadas como genre, releaseYear, rating e metadata.

Integrar consultas no app da Web

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário das seguintes importações:
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. Substitua as funções handleAuthStateChange e handleGetCurrentUser pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • handleAuthStateChange: essa função detecta mudanças no estado de autenticação. Quando um usuário faz login, ele define os dados do usuário e chama a mutação upsertUser para criar ou atualizar as informações do usuário no banco de dados.
  • handleGetCurrentUser: busca o perfil do usuário atual usando a consulta getCurrentUser, que recupera as avaliações e os filmes favoritos do usuário.

Veja isso na prática

Agora, clique no botão "Fazer login com o Google" na barra de navegação. Você pode fazer login usando o emulador do Firebase Authentication. Depois de fazer login, clique em "Meu perfil". Por enquanto, ele vai estar vazio, mas você já configurou a base para o tratamento de dados específicos do usuário no seu app.

8. Implementar interações do usuário

Nesta seção do codelab, você vai implementar interações do usuário no app de resenhas de filmes, permitindo que os usuários gerenciem os filmes favoritos e deixem ou excluam resenhas.

b3d0ac1e181c9de9.png

Permitir que um usuário marque um filme como favorito

Nesta seção, você vai configurar o banco de dados para permitir que os usuários marquem um filme como favorito.

Implementar conectores

  1. Abra mutations.gql no dataconnect/movie-connector/.
  2. Adicione as seguintes mutações para processar a ação de adicionar filmes aos favoritos:
    # 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 })
    }
    
    

Principais conclusões:

  • userId_expr: "auth.uid": usa auth.uid, que é fornecido diretamente pelo Firebase Authentication, garantindo que apenas os dados do usuário autenticado sejam acessados ou modificados.

Verificar se um filme foi adicionado aos favoritos

  1. Abra queries.gql no dataconnect/movie-connector/.
  2. Adicione a consulta a seguir para verificar se um filme foi adicionado aos favoritos:
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

Principais conclusões:

  • auth.uid: garante o acesso seguro a dados específicos do usuário usando o Firebase Authentication.
  • favorite_movie: verifica a tabela de junção favorite_movies para saber se um filme específico foi marcado como favorito pelo usuário atual.

Integrar consultas no web app

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário das seguintes importações:
    import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
    
  2. Substitua as funções handleAddFavoritedMovie, handleDeleteFavoritedMovie e handleGetIfFavoritedMovie pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • handleAddFavoritedMovie e handleDeleteFavoritedMovie: use as mutações para adicionar ou remover um filme dos favoritos do usuário com segurança.
  • handleGetIfFavoritedMovie: usa a consulta getIfFavoritedMovie para verificar se um filme foi marcado como favorito pelo usuário.

Veja isso na prática

Agora, você pode adicionar ou remover filmes dos favoritos clicando no ícone de coração nos cards e na página de detalhes do filme. Além disso, você pode conferir seus filmes favoritos na página do perfil.

Permitir que os usuários deixem ou excluam avaliações

Em seguida, você vai implementar a seção para gerenciar as avaliações dos usuários no app.

Implementar conectores

Em mutations.gql (dataconnect/movie-connector/mutations.gql): adicione as seguintes mutações:

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

Principais conclusões:

  • userId_expr: "auth.uid": garante que as avaliações estejam associadas ao usuário autenticado.
  • reviewDate_date: { today: true }: gera automaticamente a data atual da avaliação usando o DataConnect, eliminando a necessidade de entrada manual.

Integrar consultas no web app

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário das seguintes importações:
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. Substitua as funções handleAddReview e handleDeleteReview pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • handleAddReview: chama a mutação addReview para adicionar uma avaliação do filme especificado, vinculando-a com segurança ao usuário autenticado.
  • handleDeleteReview: usa a mutação deleteReview para remover uma avaliação de um filme feita pelo usuário autenticado.

Veja isso na prática

Agora os usuários podem deixar avaliações sobre filmes na página de detalhes. Eles também podem ver e excluir as avaliações na página do perfil, tendo controle total sobre as interações com o app.

9. Filtros avançados e correspondência parcial de texto

Nesta seção, você vai implementar recursos avançados de pesquisa, permitindo que os usuários pesquisem filmes com base em uma variedade de classificações e anos de lançamento, filtrem por gêneros e tags, realizem correspondência parcial de texto em títulos ou descrições e até mesmo combinem vários filtros para resultados mais precisos.

ece70ee0ab964e28.png

Implementar conectores

  1. Abra queries.gql no app dataconnect/movie-connector/.
  2. Adicione a seguinte consulta para oferecer suporte a vários recursos de pesquisa:
    # 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
        }
      }
    }
    

Principais conclusões:

  • Operador _and: combina várias condições em uma única consulta, permitindo que a pesquisa seja filtrada por vários campos, como releaseYear, rating e genre.
  • Operador contains: pesquisa correspondências parciais de texto em campos. Nessa consulta, ele procura correspondências em title, description, name ou reviewText.
  • Cláusula where: especifica as condições para filtrar dados. Cada seção (filmes, atores, avaliações) usa uma cláusula where para definir os critérios específicos da pesquisa.

Integrar consultas no web app

  1. Em MovieService (app/src/lib/MovieService.tsx), remova o comentário das seguintes importações:
    import { searchAll, SearchAllData } from "@movie/dataconnect";
    
  2. Substitua a função handleSearchAll pelo seguinte código:
    // 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;
      }
    };
    

Principais conclusões:

  • handleSearchAll: essa função usa a consulta searchAll para realizar uma pesquisa com base na entrada do usuário, filtrando os resultados por parâmetros como ano, classificação, gênero e correspondências parciais de texto.

Veja isso na prática

Acesse a página "Pesquisa avançada" na barra de navegação do web app. Agora você pode pesquisar filmes, atores e avaliações usando vários filtros e entradas, recebendo resultados de pesquisa detalhados e personalizados.

10. Opcional: implante no Cloud (é necessário ter faturamento)

Agora que você concluiu a iteração de desenvolvimento local, é hora de implantar seu esquema, dados e consultas no servidor. Isso pode ser feito usando a extensão do Firebase Data Connect para VS Code ou a CLI do Firebase.

Fazer upgrade do plano de preços do Firebase

Para integrar o Firebase Data Connect ao Cloud SQL para PostgreSQL, seu projeto do Firebase precisa estar no plano de preços Blaze de pagamento por uso, o que significa que ele está vinculado a uma conta do Cloud Billing.

  • Uma conta do Cloud Billing exige uma forma de pagamento, como cartão de crédito.
  • Se você ainda não conhece o Firebase e o Google Cloud, confira se tem qualificação para receber um crédito de US$300 e uma conta de teste sem custos financeiros do Cloud Billing.
  • Se você estiver fazendo este codelab como parte de um evento, pergunte ao organizador se há créditos do Cloud disponíveis.

Para fazer upgrade do seu projeto para o plano Blaze, siga estas etapas:

  1. No console do Firebase, selecione Fazer upgrade do seu plano.
  2. Selecione o plano Blaze. Siga as instruções na tela para vincular uma conta do Cloud Billing ao seu projeto.
    Se você precisou criar uma conta do Cloud Billing como parte desse upgrade, talvez seja necessário voltar para o fluxo de upgrade no console do Firebase para concluir o processo.

Conecte seu app da Web ao projeto do Firebase

  1. Registre seu app da Web no projeto do Firebase usando o console do Firebase:
    1. Abra seu projeto e clique em Adicionar app.
    2. Ignore a configuração do SDK por enquanto, mas copie o objeto firebaseConfig gerado.
    7030822793e4d75b.png
  2. Substitua o firebaseConfig atual em app/src/lib/firebase.tsx pela configuração que você acabou de copiar do console do 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. Crie o web app:de volta ao VS Code, na pasta app, use o Vite para criar o web app para implantação de hospedagem:
    cd app
    npm run build
    

Configurar o Firebase Authentication no seu projeto do Firebase

  1. Configure o Firebase Authentication com o Login do Google.62af2f225e790ef6.png
  2. (Opcional) Permita domínios para o Firebase Authentication usando o console do Firebase (por exemplo, http://127.0.0.1).
    1. Nas configurações de Autenticação, acesse Domínios autorizados.
    2. Clique em "Adicionar domínio" e inclua seu domínio local na lista.

c255098f12549886.png

Implantar com a CLI do Firebase

  1. Em dataconnect/dataconnect.yaml, verifique se o ID da instância, o banco de dados e o ID do serviço correspondem ao seu projeto:
    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. Verifique se a CLI do Firebase está configurada com seu projeto:
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
    
  3. No terminal, execute o seguinte comando para implantar:
    firebase deploy --only dataconnect,hosting
    
  4. Execute este comando para comparar as mudanças no esquema:
    firebase dataconnect:sql:diff
    
  5. Se as mudanças forem aceitáveis, aplique-as com:
    firebase dataconnect:sql:migrate
    

Sua instância do Cloud SQL para PostgreSQL será atualizada com o esquema e os dados finais implantados. É possível monitorar o status no console do Firebase.

Agora você poderá ver o app em your-project.web.app/. Além disso, clique em Executar (produção) no painel do Firebase Data Connect, assim como fez com os emuladores locais, para adicionar dados ao ambiente de produção.

11. Opcional: pesquisa de vetores com o Firebase Data Connect (é necessário faturamento)

Nesta seção, você vai ativar a pesquisa vetorial no app de avaliação de filmes usando o Firebase Data Connect. Esse recurso permite pesquisas baseadas em conteúdo, como encontrar filmes com descrições semelhantes usando embeddings de vetor.

Esta etapa exige que você tenha concluído a última etapa deste codelab para fazer a implantação no Google Cloud.

4b5aca5a447d2feb.png

Atualizar o esquema para incluir incorporações de um campo

Em dataconnect/schema/schema.gql, adicione o campo descriptionEmbedding à tabela Movie:

type Movie
  # The below parameter values are generated by default with @table, and can be edited manually.
  @table {
  # implicitly calls @col to generates a column name. ex: @col(name: "movie_id")
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
  descriptionEmbedding: Vector @col(size:768) # Enables vector search
}

Principais conclusões:

  • descriptionEmbedding: Vector @col(size:768): esse campo armazena os embeddings semânticos das descrições de filmes, permitindo a pesquisa de conteúdo baseada em vetores no seu app.

Ativar a Vertex AI

  1. Siga o guia de pré-requisitos para configurar as APIs da Vertex AI no Google Cloud. Essa etapa é essencial para oferecer suporte à geração de embeddings e à funcionalidade de pesquisa vetorial.
  2. Implante novamente seu esquema para ativar a pgvector e a pesquisa de vetores clicando em "Implantar na produção" usando a extensão do Firebase Data Connect VS Code.

Preencher o banco de dados com embeddings

  1. Abra a pasta dataconnect no VS Code.
  2. Clique em Executar(local) em optional_vector_embed.gql para preencher seu banco de dados com incorporações dos filmes.

b858da780f6ec103.png

Adicionar uma consulta de pesquisa vetorial

Em dataconnect/movie-connector/queries.gql, adicione a seguinte consulta para fazer pesquisas de vetores:

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

Principais conclusões:

  • compare_embed: especifica o modelo de embedding (textembedding-gecko@003) e o texto de entrada ($query) para comparação.
  • method: especifica o método de similaridade (L2), que representa a distância euclidiana.
  • within: limita a pesquisa a filmes com uma distância L2 de 2 ou menos, focando em correspondências de conteúdo próximas.
  • limit: restringe o número de resultados retornados a 5.

Implementar a função de pesquisa vetorial no app

Agora que o esquema e a consulta estão configurados, integre a pesquisa vetorial à camada de serviço do seu app. Essa etapa permite chamar a consulta de pesquisa do seu web app.

  1. Em app/src/lib/ MovieService.ts, remova o comentário das seguintes importações dos SDKs. Isso vai funcionar como qualquer outra consulta.
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. Adicione a seguinte função para integrar a pesquisa baseada em vetores ao app:
    // 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;
      }
    };
    

Principais conclusões:

  • searchMoviesByDescription: essa função chama a consulta searchMovieDescriptionUsingL2similarity, transmitindo o texto de entrada para realizar uma pesquisa de conteúdo baseada em vetor.

Veja isso na prática

Navegue até a seção "Pesquisa vetorial" na barra de navegação e digite frases como "romântico e moderno". Uma lista de filmes que correspondem ao conteúdo que você está procurando vai aparecer. Ou acesse a página de detalhes de qualquer filme e confira a seção "Filmes semelhantes" na parte de baixo da página.

7b71f1c75633c1be.png

12. Conclusão

Parabéns! Agora você pode usar o web app. Se quiser usar seus próprios dados de filmes, não se preocupe. Insira seus dados usando a extensão do Firebase Data Connect imitando os arquivos _insert.gql ou adicione-os pelo painel de execução do Data Connect no VS Code.

Saiba mais