Criar com o Firebase Data Connect

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 análise de filmes. O aplicativo concluído mostra como o Firebase Data Connect simplifica o processo de criação de aplicativos com tecnologia SQL. Ele inclui os seguintes recursos:

  • Autenticação: implemente a autenticação personalizada para as 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 app da Web de análise de filmes.
  • Consultas e mutações SQL: recupere, atualize e gerencie dados no Cloud SQL usando consultas e mutações com a 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 de vetores: adicione a funcionalidade de pesquisa de conteúdo usando a pesquisa de vetores do Firebase Data Connect para oferecer uma experiência de usuário rica com base nas entradas e preferências.

Pré-requisitos

É preciso ter um conhecimento básico de JavaScript.

O que você aprenderá

  • Configurar o Firebase Data Connect com emuladores locais.
  • Projete um esquema de dados usando o Data Connect e o GraphQL.
  • Crie e teste várias consultas e mutações para um app de análise 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 você vai precisar

Como configurar o ambiente para desenvolvedores

Esta seção vai orientar você na configuração do ambiente para começar a criar seu app de análise de filmes usando o Firebase Data Connect.

Etapa 1: clonar o repositório do projeto

Comece clonando o repositório do projeto e instalando 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
  1. Depois de executar os comandos, abra http://localhost:5173 no seu navegador para ver o app da Web sendo executado localmente. Ele serve como front-end para criar o app de crítica de filmes e interagir com os recursos dele.

93f6648a2532c606.png

Etapa 2: abrir o projeto no Visual Studio Code

Abra a pasta clonada codelab-dataconnect-web usando o Visual Studio Code. É aqui que você define seu esquema, escreve consultas e testa a funcionalidade do app.

Etapa 3: instalar a extensão do Visual Studio do Firebase Data Connect

Para usar os recursos do Data Connect, instale a extensão do Visual Studio do Firebase Data Connect.Você também pode fazer isso no Marketplace do Visual Studio Code ou pesquisar no VS Code.

  1. Ou instalar pelo Visual Studio Code Marketplace ou pesquisar no VS Code.

b03ee38c9a81b648.png

Etapa 4: criar um projeto do Firebase

Acesse o Console do Firebase para criar um novo projeto do Firebase, caso ainda não tenha um. Em seguida, na extensão do VSCode do Firebase Data Connect:

  • Clique no botão Fazer login.
  • Clique em Conectar um projeto do Firebase e selecione o projeto que você criou no console do Firebase.

4bb2fbf8f9fac29b.png

Etapa 5: iniciar emuladores do Firebase

Na extensão do VSCode do Firebase Data Connect, clique em "Start Emulators" e confirme se os emuladores estão em execução no terminal.

6d3d95f4cb708db1.png

2. 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

Confira uma visão geral rápida da estrutura de pastas e arquivos do app:

conexão de dados/

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 app.
  • connector/mutations.gql: mutações necessárias no app.
  • connector/connector.yaml: Arquivo de configuração para geração do SDK

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 console.
  • lib/dataconnect-sdk/: essa pasta 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.

3. Como definir um esquema para a análise de filmes

Nesta seção, você vai definir a estrutura e as relações entre as principais entidades no aplicativo de filme em um esquema. Entidades como Movie, User, Actor e Review são mapeadas para tabelas de banco de dados, com relacionamentos estabelecidos usando diretivas de esquema do Firebase Data Connect e GraphQL. Quando estiver pronto, seu app estará pronto para lidar com tudo, desde pesquisar filmes com boas avaliações e filtrar por gênero até permitir que os usuários façam avaliações, marquem favoritos, explorem filmes semelhantes ou encontrem filmes recomendados com base em entrada de texto por meio da pesquisa vetorial.

Entidades e relações 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 as interações do usuário, como avaliações e adição aos favoritos. Reviews conectam usuários a filmes, permitindo que o app mostre classificações e feedback gerados pelo usuário.

As relações entre filmes, atores e usuários tornam o app mais dinâmico. A tabela de mesclagem MovieActor ajuda a mostrar detalhes do elenco e filmografias de atores. O tipo FavoriteMovie permite que os usuários marquem filmes como favoritos, para que o app possa mostrar uma lista personalizada de favoritos e destacar as opções mais populares.

Tabela de filmes

O tipo "Filme" define a estrutura principal de uma entidade de filme, incluindo campos como título, gênero, ano de lançamento e classificação.

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

Aprendizados importantes:

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

Tabela MovieMetadata

O tipo MovieMetadata estabelece uma relação um para um com o tipo Movie. Ele inclui dados adicionais, 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
}

Aprendizados importantes:

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

Tabela de atores

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.

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

Aprendizados importantes:

  • movie: faz referência ao tipo "Movie", gera implicitamente uma chave estrangeira movieId: UUID!.
  • actor: faz referência ao tipo de ator e gera implicitamente uma chave estrangeira actorId: UUID.
  • role:define o papel do ator no filme (por exemplo, "principal" ou "apoio").

Tabela de usuários

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

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

Tabela FavoriteMovie

O tipo FavoriteMovie é uma tabela de mesclagem que processa relacionamentos de muitos para muitos entre usuários e os filmes ou atores favoritos deles. 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!
}

Aprendizados importantes:

  • 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.

Tabela de revisão

O tipo de resenha representa a entidade da resenha e vincula os tipos Usuário e Filme em um relacionamento de muitos para muitos (um usuário pode deixar muitas resenhas, e cada filme pode ter muitas críticas).

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

Aprendizados importantes:

  • user: faz referência ao usuário que deixou a avaliação.
  • movie: faz referência ao filme que está sendo avaliado.
  • reviewDate: definida automaticamente como o horário 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 "Filme" e "Avaliação" é preenchido automaticamente com um UUID quando um novo registro é criado.

Agora que o esquema está definido, seu app de filmes tem uma base sólida para a estrutura de dados e as relações.

4. Como recuperar filmes principais e mais recentes

App FriendlyMovies

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

Como inserir dados de filmes, atores e resenhas

  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 Run (local) na parte de cima do arquivo. Clique aqui para inserir os dados fictícios do filme no banco de dados.

e424f75e63bf2e10.png

  1. Verifique o terminal Data Connect Execution para confirmar se os dados foram adicionados.

e0943d7704fb84ea.png

Como implementar o conector

  1. Abra dataconnect/movie-connector/queries.gql. Você vai encontrar uma consulta ListMovies básica nos comentários:
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

Esta consulta busca todos os filmes e seus detalhes (por exemplo, id, title, releaseYear). No entanto, ele não classifica os filmes.

  1. Substitua a consulta ListMovies pela consulta abaixo 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
  }
}

Clique no botão Executar (local) para executar a consulta no seu banco de dados local. Também é possível inserir as variáveis de consulta no painel de configuração antes da execução.

c4d947115bb11b16.png

Aprendizados importantes:

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

Como integrar consultas no app da Web

Nesta parte, 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 nos arquivos .gql (schema.gql, query.gql, Muts.gql) e Connector.yaml. Esses SDKs podem ser chamados diretamente no aplicativo.

  1. Em MovieService (app/src/lib/MovieService.tsx), descomente a instruçã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 o tipo enumerado OrderDirection são SDKs gerados pelos emuladores do Firebase Data Connect com base no esquema e nas consultas que você definiu anteriormente.

  1. 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;
  }
};

Aprendizados importantes:

  • 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 exibir os 10 principais filmes e os mais recentes na página inicial.

Veja na prática

Atualize seu app da Web para ver a consulta em ação. A página inicial agora mostra dinamicamente a lista de filmes, buscando dados diretamente do seu banco de dados local. Você verá os filmes mais bem avaliados e os mais recentes aparecerem perfeitamente, refletindo os dados que você acabou de configurar.

5. Como mostrar detalhes de filmes e atores

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

ac7fefa7ff779231.png

Como implementar conectores

  1. Abra dataconnect/movie-connector/queries.gql no seu projeto.
  2. Adicione as seguintes consultas para extrair 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
    }
  }
}
  1. Salve as mudanças e revise as consultas.

Aprendizados importantes:

  • movie()/actor(): campos de consulta do GraphQL para buscar um único filme ou ator da tabela "Movies" ou "Actors".
  • _on_: permite o acesso direto a campos de um tipo associado que tenha um relacionamento de chave externa. Por exemplo, reviews_on_movie busca todas as avaliações relacionadas a um filme específico.
  • _via_: usada para navegar em relacionamentos de muitos para muitos por meio de uma tabela de mesclagem. Por exemplo, actors_via_MovieActor acessa o tipo "Ator" pela tabela de junção MovieActor, e a condição where filtra atores com base no papel (por exemplo, "principal" ou "apoio").

No painel de execução do Data Connect, é possível testar a consulta inserindo IDs falsos, como:

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

Clique em Run (local) em GetMovieById para recuperar os detalhes sobre "Quantum Paradox" (Paradoxo quântico) (o filme simulado com o qual o ID acima se refere).

1b08961891e44da2.png

Como integrar consultas no app da Web

  1. Em MovieService (app/src/lib/MovieService.tsx), remova a marca de comentário das seguintes importações:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. 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;
  }
};

Aprendizados importantes:

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

Confira na prática

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

6. Como processar a autenticação do usuário

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

9890838045d5a00e.png

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

Aprendizado importante:

  • id_expr: "auth.uid": usa o 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 tratado de maneira segura e automática.

Em seguida, abra queries.gql no dataconnect/movie-connector/.

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

Pontos-chave:

  • auth.uid: é extraído diretamente do Firebase Authentication, garantindo o acesso seguro a dados específicos do usuário.
  • _on_ Campos: esses campos representam as tabelas mescladas:
  • reviews_on_user: busca todas as avaliações relacionadas ao usuário, incluindo o ID e o título do filme.
  • favorite_movies_on_user: recupera todos os filmes marcados como favoritos pelo usuário, incluindo informações detalhadas como gênero, lançamentoAno, classificação e metadados.

Como integrar consultas no app da Web

  1. No MovieService (app/src/lib/MovieService.tsx), remova a marca de comentário das seguintes importações:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. 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;
  }
};

Aprendizados importantes:

  • handleAuthStateChange: esta função detecta alterações 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 do usuário e os filmes favoritos dele.

Veja 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 Auth. Depois de fazer login, clique em "Meu perfil". Por enquanto, ele vai estar vazio, mas você já configurou a base para o processamento de dados específico do usuário no app.

7. Como implementar interações do usuário

Nesta seção, você vai implementar interações do usuário no app de crítica de filmes. Assim, os usuários poderão gerenciar os filmes favoritos deles e deixar ou excluir avaliações.

b3d0ac1e181c9de9.png

Como implementar conectores

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

Aprendizados importantes:

  • 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.
  1. Em seguida, abra queries.gql no dataconnect/movie-connector/.
  2. Adicione a consulta abaixo para verificar se um filme é favorito:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}

Aprendizados importantes:

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

Como integrar consultas no app da Web

  1. Em MovieService (app/src/lib/MovieService.tsx), remova a marca de comentário das seguintes importações:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. 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;
  }
};

Aprendizados importantes:

  • 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 na prática

Agora, você pode marcar filmes como favoritos ou removê-los clicando no ícone de coração nos cards e na página de detalhes do filme. Além disso, você pode visualizar seus filmes favoritos na página de seu perfil.

Como implementar avaliações de usuários

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

Como implementar conectores

  1. 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 })
}

Aprendizados importantes:

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

Como integrar consultas no app da Web

  1. Em MovieService (app/src/lib/MovieService.tsx), remova a marca de comentário das seguintes importações:
import { addReview, deleteReview } from "@movie/dataconnect";
  1. 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;
  }
};

Aprendizados importantes:

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

Veja na prática

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

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

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é combinem vários filtros para resultados mais precisos.

ece70ee0ab964e28.png

Como implementar conectores

  1. Abrir queries.gql no app dataconnect/movie-connector/.
  2. Adicione a consulta a seguir 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
    }
  }
}

Aprendizados importantes:

  • 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 de texto parcial nos 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, críticas) usa uma cláusula where para definir os critérios específicos da pesquisa.

Como integrar consultas no app da Web

  1. Em MovieService (app/src/lib/MovieService.tsx), descomente as seguintes importações:
import { searchAll, SearchAllData } from "@movie/dataconnect";
  1. Substitua a função handleSearchAll por este 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;
  }
};

Aprendizados importantes:

  • 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 na prática

Acesse a "Pesquisa avançada" página da barra de navegação no aplicativo da web. Agora você pode pesquisar filmes, atores e avaliações usando vários filtros e entradas para receber resultados detalhados e personalizados.

9. Opcional: implantar na nuvem (requer cobrança)

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

Como adicionar um app da Web ao Console do Firebase

  1. Crie um app da Web no Console do Firebase e anote o ID do app.

7030822793e4d75b.png

  1. Configure um app da Web no console do Firebase clicando em "Adicionar app". Por enquanto, você pode ignorar a configuração do SDK e a configuração, mas anote o objeto firebaseConfig gerado.
  2. Substitua o firebaseConfig em app/src/lib/firebase.tsx:
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. Crie o app da Web: na pasta app, use o Vite para criar o app da Web que hospedará a implantação:
cd app
npm run build

Configurar o Firebase Authentication no console

  1. Configurar o Firebase Auth com o Login do Google

62af2f225e790ef6.png

  1. (Opcional) Permita domínios para (Firebase Auth) [https://firebase.google.com/docs/auth/web/hosting] no console do projeto (por exemplo, http://127.0.0.1):

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"]
  1. Verifique se a CLI do Firebase está configurada com seu projeto
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. No terminal, execute o seguinte comando para implantar:
firebase deploy --only dataconnect,hosting
  1. Execute este comando para comparar as alterações no esquema:
firebase dataconnect:sql:diff
  1. 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ê pode acessar o app em your-project.web.app/. Além disso, você pode clicar 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.

10. Opcional: pesquisa de vetores com o Firebase Data Connect

Nesta seção, você vai ativar a pesquisa de vetores no seu app de análise 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.

4b5aca5a447d2feb.png

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

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

Aprendizado importante:

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

Como ativar a Vertex AI

  1. Siga o guia de pré-requisitos para configurar as APIs Vertex AI com o Google Cloud. Essa etapa é essencial para oferecer suporte à geração de embedding e à funcionalidade de pesquisa vetorial.
  2. Reimplante seu esquema para ativar a pesquisa de pgvector e vetor clicando em "Implantar na produção" usando a extensão do VSCode do Firebase Data Connect.

Como preencher o banco de dados com embeddings

  1. Abra a pasta dataconnect no VSCode e clique em Run(local) em optional_vector_embed.gql para preencher o banco de dados com incorporações dos filmes.

b858da780f6ec103.png

Adicionar uma consulta de pesquisa de vetor

  1. Em dataconnect/movie-connector/queries.gql, adicione a seguinte consulta para realizar pesquisas vetoriais:
# 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
  }
}

Aprendizados importantes:

  • 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, com foco em correspondências de conteúdo próximas.
  • limit: restringe o número de resultados retornados a 5.

Implementar a função de pesquisa de vetor no app

  1. Em app/src/lib/MovieService.ts, remova a marca de comentário das seguintes importações:

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 app. Essa etapa permite chamar a consulta de pesquisa do seu app da Web.

No app/src/lib/ MovieService.ts, remova a marca de comentário das seguintes importações dos SDKs. Isso vai funcionar como qualquer outra consulta.

import {
  searchMovieDescriptionUsingL2similarity,
  SearchMovieDescriptionUsingL2similarityData,
} from "@movie/dataconnect";

Adicione a função a seguir 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;
  }
};


Aprendizados importantes:

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

Veja na prática

Navegue até "Pesquisa Vetorial". na barra de navegação e digite frases como "romântico e moderno". Você vai encontrar uma lista de filmes que correspondem ao conteúdo que você está procurando ou pode acessar a página de detalhes de qualquer filme e conferir a seção de filmes semelhantes na parte de baixo da página.

7b71f1c75633c1be.png

11. Conclusão

Parabéns, você já pode usar o app da Web. Se você quiser usar seus próprios dados de filmes, não se preocupe. Insira seus dados usando a extensão do FDC, imitando os arquivos _insert.gql ou adicionando-os pelo painel de execução do Data Connect.

Saiba mais