Compila con Firebase Data Connect

1. Antes de comenzar

App de FriendlyMovies

En este codelab, integrarás Firebase Data Connect con una base de datos de Cloud SQL para compilar una app web de opiniones sobre películas. La app completa muestra cómo Firebase Data Connect simplifica el proceso de compilación de aplicaciones potenciadas por SQL. Incluye las siguientes funciones:

  • Autenticación: Implementa la autenticación personalizada para las consultas y mutaciones de tu app, lo que garantiza que solo los usuarios autorizados puedan interactuar con tus datos.
  • Esquema de GraphQL: Crea y administra tus estructuras de datos con un esquema de GraphQL flexible adaptado a las necesidades de una app web de opiniones de películas.
  • Consultas y mutaciones de SQL: Recupera, actualiza y administra datos en Cloud SQL a través de consultas y mutaciones con la tecnología de GraphQL.
  • Búsqueda avanzada con coincidencia parcial de cadenas: Usa filtros y opciones de búsqueda para encontrar películas según campos como el título, la descripción o las etiquetas.
  • Integración de búsqueda de vectores (opcional): Agrega la funcionalidad de búsqueda de contenido con la búsqueda de vectores de Firebase Data Connect para proporcionar una experiencia del usuario enriquecida según las entradas y las preferencias.

Requisitos previos

Necesitarás conocimientos básicos de JavaScript.

Qué aprenderá

  • Configura Firebase Data Connect con emuladores locales.
  • Diseña un esquema de datos con Data Connect y GraphQL.
  • Escribe y prueba varias consultas y mutaciones para una app de opiniones sobre películas.
  • Obtén información sobre cómo Firebase Data Connect genera y usa el SDK en la app.
  • Implementa tu esquema y administra la base de datos de forma eficiente.

Requisitos

Configura el entorno de desarrollo

En esta sección, se te guiará para configurar el entorno y comenzar a compilar tu app de opiniones de películas con Firebase Data Connect.

Paso 1: Clona el repositorio del proyecto

Comienza por clonar el repositorio del proyecto y, luego, instala las dependencias necesarias:

git clone https://github.com/firebaseextended/codelab-dataconnect-web
cd codelab-dataconnect-web
cd ./app && npm i
npm run dev
  1. Después de ejecutar estos comandos, abre http://localhost:5173 en tu navegador para ver la aplicación web que se ejecuta de manera local. Esto funciona como tu frontend para crear la app de reseñas de películas y para interactuar con sus funciones.

93f6648a2532c606.png

Paso 2: Abre el proyecto en Visual Studio Code

Abre la carpeta codelab-dataconnect-web clonada con Visual Studio Code. Aquí es donde definirás el esquema, escribirás consultas y probarás la funcionalidad de la app.

Paso 3: Instala la extensión de Visual Studio para Firebase Data Connect

Para usar las funciones de Data Connect, instala la extensión de Visual Studio para Firebase Data Connect. También puedes instalarla desde Visual Studio Code Marketplace o buscarla en VS Code.

  1. También puedes instalarlo desde Visual Studio Code Marketplace o buscarlo en VS Code.

b03ee38c9a81b648.png

Paso 4: Crea un proyecto de Firebase

Ve a Firebase console para crear un proyecto de Firebase nuevo si aún no tienes uno. Luego, haz lo siguiente en la extensión de Firebase Data Connect VSCode:

  • Haz clic en el botón Acceder.
  • Haz clic en Conectar un proyecto de Firebase y selecciona el proyecto que creaste en Firebase console.

4bb2fbf8f9fac29b.png

Paso 5: Inicia los emuladores de Firebase

En la extensión de VSCode de Firebase Data Connect, haz clic en Iniciar emuladores y confirma que los emuladores se estén ejecutando en la terminal.

6d3d95f4cb708db1.png

2. Revisa la base de código de partida

En esta sección, explorarás las áreas clave de la base de código de partida de la app. Si bien a la app le faltan algunas funciones, es útil comprender la estructura general.

Estructura de carpetas y archivos

A continuación, se incluye una descripción general de la estructura de carpetas y archivos de la app:

dataconnect/

Contiene configuraciones de Firebase Data Connect, conectores (que definen consultas y mutaciones) y archivos de esquemas.

  • schema/schema.gql: Define el esquema de GraphQL.
  • connector/queries.gql: Son las consultas necesarias en tu app.
  • connector/mutations.gql: Se necesitan mutaciones en tu app.
  • Archivo de configuración connector/connector.yaml: para la generación de SDK

app/src/

Contiene la lógica de la aplicación y la interacción con Firebase Data Connect.

  • firebase.ts: Configuración para conectarse a una app de Firebase en la consola.
  • lib/dataconnect-sdk/: Esta carpeta contiene el SDK generado. Puedes editar la ubicación de la generación del SDK en el archivo connectedor/connector.yaml y los SDKs se generarán automáticamente cada vez que definas una consulta o mutación.

3. Cómo definir un esquema para la revisión de una película

En esta sección, definirás la estructura y las relaciones entre las entidades clave de la aplicación de películas en un esquema. Las entidades como Movie, User, Actor y Review se asignan a tablas de bases de datos, con relaciones establecidas mediante las directivas de esquema Firebase Data Connect y GraphQL. Una vez que se implemente, tu app estará lista para realizar todo tipo de tareas, desde buscar las películas con mejor calificación y filtrar por género hasta permitir que los usuarios dejen opiniones, marquen como favoritas, exploren películas similares o busquen películas recomendadas en función de la entrada de texto a través de la búsqueda de vectores.

Entidades y relaciones principales

El tipo Movie contiene detalles clave, como el título, el género y las etiquetas, que la app usa para búsquedas y perfiles de películas. El tipo User realiza un seguimiento de las interacciones del usuario, como opiniones y favoritos. Reviews conecta a los usuarios con películas, lo que permite que la app muestre calificaciones y comentarios generados por los usuarios.

Las relaciones entre las películas, los actores y los usuarios hacen que la app sea más dinámica. La tabla de unión MovieActor ayuda a mostrar los detalles del elenco y las filmografías de los actores. El tipo FavoriteMovie permite a los usuarios marcar como favoritas películas, de modo que la app puede mostrar una lista de favoritos personalizada y destacar selecciones populares.

Tabla de películas

El tipo "movie" define la estructura principal de una entidad "movie", incluidos campos como title, genre, releaseYear y rating.

Copia y pega el fragmento de código en tu archivo 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]
}

Conclusiones clave:

  • id: Es un UUID único para cada película, generado con @default(expr: "uuidV4()").

Tabla MovieMetadata

El tipo MovieMetadata establece una relación uno a uno con el tipo Movie. Incluye datos adicionales, como el director de la película.

Copia y pega el fragmento de código en tu archivo 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
}

Conclusiones clave:

  • Película @ref: Hace referencia al tipo Movie y establece una relación de clave externa.

Tabla de actores

Copia y pega el fragmento de código en tu archivo dataconnect/schema/schema.gql:

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

El tipo Actor representa a un actor en la base de datos de películas, en la que cada actor puede ser parte de varias películas, lo que forma una relación de varios a varios.

Tabla MovieActor

Copia y pega el fragmento de código en tu archivo 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"
}

Conclusiones clave:

  • movie: Hace referencia al tipo de película y genera implícitamente una clave externa movieId: UUID!.
  • actor: Hace referencia al tipo de actor y genera implícitamente una clave externa actorId: UUID!.
  • role: Define el rol del actor en la película (p.ej., “principal” o "apoyar").

Tabla de usuarios

El tipo User define una entidad de usuario que interactúa con las películas dejando opiniones o marcando películas como favoritas.

Copia y pega el fragmento de código en tu archivo 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
}

Tabla FavoriteMovie

El tipo FavoriteMovie es una tabla de unión que controla las relaciones de varios a varios entre los usuarios y sus películas o actores favoritos. Cada tabla vincula un User a un Movie.

Copia y pega el fragmento de código en tu archivo 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!
}

Conclusiones clave:

  • movie: Hace referencia al tipo Movie, genera implícitamente una clave externa movieId: UUID!.
  • user: Hace referencia al tipo de usuario y genera implícitamente una clave externa userId: UUID!.

Tabla de revisión

El tipo de opinión representa la entidad de opinión y vincula los tipos de usuario y película en una relación de varios a varios (un usuario puede dejar muchas opiniones, y cada película puede tener muchas).

Copia y pega el fragmento de código en tu archivo 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")
}

Conclusiones clave:

  • user: Hace referencia al usuario que dejó la opinión.
  • movie: Hace referencia a la película que se está revisando.
  • reviewDate: Se establece automáticamente en la hora en la que se crea la opinión con @default(expr: "request.time").

Valores predeterminados y campos generados automáticamente

El esquema usa expresiones como @default(expr: "uuidV4()") para generar automáticamente IDs y marcas de tiempo únicos. Por ejemplo, el campo id en los tipos Película y Opinión se completa automáticamente con un UUID cuando se crea un registro nuevo.

Ahora que el esquema está definido, tu app de películas tiene una base sólida para su estructura y relaciones de datos.

4. Cómo recuperar las películas principales y las más recientes

App de FriendlyMovies

En esta sección, insertarás datos de películas ficticias en los emuladores locales y, luego, implementarás los conectores (consultas) y el código de TypeScript para llamar a esos conectores en la aplicación web. Al final, tu app podrá recuperar y mostrar de forma dinámica las películas con mejor calificación y las más recientes directamente desde la base de datos.

Cómo insertar datos de películas, actores y opiniones simulados

  1. En VSCode, abre dataconnect/moviedata_insert.gql. Asegúrate de que se estén ejecutando los emuladores en la extensión de Firebase Data Connect.
  2. Deberías ver el botón Run (local) en la parte superior del archivo. Haz clic aquí para insertar los datos de películas simuladas en tu base de datos.

e424f75e63bf2e10.png

  1. Verifica la terminal Data Connect Execution para confirmar que los datos se agregaron correctamente.

e0943d7704fb84ea.png

Cómo implementar el conector

  1. Abre dataconnect/movie-connector/queries.gql. Encontrarás una consulta ListMovies básica en los comentarios:
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

Esta consulta recupera todas las películas y sus detalles (p.ej., id, title, releaseYear). Sin embargo, no ordena las películas.

  1. Reemplaza la consulta ListMovies por la siguiente para agregar opciones de ordenamiento y límite:
# 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
  }
}

Haz clic en el botón Ejecutar (local) para ejecutar la consulta en tu base de datos local. También puedes ingresar las variables de consulta en el panel de configuración antes de ejecutarlas.

c4d947115bb11b16.png

Conclusiones clave:

  • movies(): Es el campo de consulta de GraphQL para recuperar datos de películas de la base de datos.
  • orderByRating: Es un parámetro para ordenar las películas por calificación (ascendente o descendente).
  • orderByReleaseYear: Es un parámetro para ordenar las películas por año de lanzamiento (ascendente o descendente).
  • limit: Restringe la cantidad de películas que se muestran.

Integración de consultas en la app web

En esta parte, usarás las consultas definidas en la sección anterior en tu app web. Los emuladores de Firebase Data Connect generan SDKs en función de la información de los archivos .gql (schema.gql, queries.gql, mutations.gql) y connector.yaml. Se puede llamar a estos SDKs directamente en tu aplicación.

  1. En MovieService (app/src/lib/MovieService.tsx), quita la marca de comentario de la sentencia de importación en la parte superior:
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

La función listMovies, el tipo de respuesta ListMoviesData y la enumeración OrderDirection son todos SDKs que generan los emuladores de Firebase Data Connect según el esquema y las consultas que definiste anteriormente.

  1. Reemplaza las funciones handleGetTopMovies y handleGetLatestMovies por el siguiente 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;
  }
};

Conclusiones clave:

  • listMovies: Es una función generada automáticamente que llama a la consulta listMovies para recuperar una lista de películas. Incluye opciones para ordenar por calificación o año de publicación y limitar la cantidad de resultados.
  • ListMoviesData: Es el tipo de resultado que se usa para mostrar las 10 principales y las últimas películas en la página principal.

Descubre cómo funciona

Vuelve a cargar tu app web para ver la consulta en acción. La página principal ahora muestra de manera dinámica la lista de películas y recupera datos directamente de tu base de datos local. Verás que las películas con mejor calificación y las más recientes aparecen sin interrupciones y reflejan los datos que acabas de configurar.

5. Cómo mostrar detalles de películas y actores

En esta sección, implementarás la funcionalidad para recuperar información detallada de una película o un actor mediante sus ID únicos. Esto implica no solo recuperar datos de sus respectivas tablas, sino también unir tablas relacionadas para mostrar detalles completos, como críticas de películas y filmografías de actores.

ac7fefa7ff779231.png

Implementación de conectores

  1. Abre dataconnect/movie-connector/queries.gql en tu proyecto.
  2. Agrega las siguientes consultas para recuperar los detalles de las películas y los actores:
# 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. Guarda los cambios y revisa las consultas.

Conclusiones clave:

  • movie()/actor(): Son campos de consulta de GraphQL para recuperar una sola película o actor de la tabla Movies o Actors.
  • _on_: Esto permite el acceso directo a los campos de un tipo asociado que tiene una relación de clave externa. Por ejemplo, reviews_on_movie recupera todas las opiniones relacionadas con una película específica.
  • _via_: Se usa para navegar por relaciones de muchos a muchos a través de una tabla de unión. Por ejemplo, actors_via_MovieActor accede al tipo de actor a través de la tabla de unión MovieActor, y la condición where filtra a los actores según su rol (p. ej., “principal” o "apoyar").

En el panel de ejecución de Conexión de datos, puedes probar la consulta ingresando los IDs de prueba, como los siguientes:

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

Haz clic en Run (local) para que GetMovieById recupere los detalles de “Quantum Paradox”. (la película simulada con la que se relaciona el ID anterior).

1b08961891e44da2.png

Integración de consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita la marca de comentario de las siguientes importaciones:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. Reemplaza las funciones handleGetMovieById y handleGetActorById por el siguiente 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;
  }
};

Conclusiones clave:

  • getMovieById/getActorById: Son funciones generadas automáticamente que llaman a las consultas que definiste y recuperan información detallada de una película o un actor específicos.
  • GetMovieByIdData / GetActorByIdData: Estos son los tipos de resultados que se usan para mostrar detalles de películas y actores en la app.

Observa cómo funciona

Ahora, ve a la página principal de tu aplicación web. Haz clic en una película y podrás ver todos sus detalles, incluidos los actores y las opiniones, información extraída de tablas relacionadas. Del mismo modo, si haces clic en un actor, verás las películas de las que formaba parte.

6. Cómo controlar la autenticación de usuarios

En esta sección, implementarás la funcionalidad de acceso y cierre de sesión de los usuarios con Firebase Authentication. También usarás los datos de Firebase Authentication para recuperar o insertar datos de usuarios directamente en Firebase DataConnect, lo que garantiza una administración de usuarios segura en tu app.

9890838045d5a00e.png

Implementación de conectores

  1. Abre mutations.gql en dataconnect/movie-connector/.
  2. Agrega la siguiente mutación para crear o actualizar el usuario autenticado actual:
# Create or update the current authenticated user
mutation UpsertUser($username: String!) @auth(level: USER) {
  user_upsert(
    data: {
      id_expr: "auth.uid"
      username: $username
    }
  )
}

Conclusión clave:

  • id_expr: "auth.uid": Usa auth.uid, proporcionado directamente por Firebase Authentication, no por el usuario ni por la app, lo que agrega una capa adicional de seguridad, ya que garantiza que el ID de usuario se controle automáticamente y de forma segura.

A continuación, abre queries.gql en dataconnect/movie-connector/.

Agrega la siguiente consulta para recuperar el usuario actual:

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

Conclusiones principales:

  • auth.uid: Se recupera directamente desde Firebase Authentication, lo que garantiza un acceso seguro a los datos específicos del usuario.
  • Campos _on_: Estos campos representan las tablas de unión:
  • reviews_on_user: Recupera todas las opiniones relacionadas con el usuario, incluido el ID y el título de la película.
  • favorite_movies_on_user: Recupera todas las películas que el usuario marcó como favoritas, incluida la información detallada, como el género, el año de lanzamiento, la calificación y los metadatos.

Cómo integrar consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de las siguientes importaciones:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. Reemplaza las funciones handleAuthStateChange y handleGetCurrentUser por el siguiente 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;
  }
};

Conclusiones clave:

  • handleAuthStateChange: Esta función detecta cambios en el estado de autenticación. Cuando un usuario accede, configura los datos del usuario y llama a la mutación upsertUser para crear o actualizar la información del usuario en la base de datos.
  • handleGetCurrentUser: Recupera el perfil del usuario actual mediante la consulta getCurrentUser, que recupera las opiniones y las películas favoritas del usuario.

Observa cómo funciona

A continuación, haz clic en "Acceder con Google" de la barra de navegación. Puedes acceder con el emulador de Firebase Auth. Después de acceder, haz clic en "Mi perfil". Por ahora, estará vacía, pero ya estableciste las bases para el manejo de datos específicos del usuario en tu app.

7. Cómo implementar interacciones con los usuarios

En esta sección, implementarás interacciones del usuario en la app de opiniones sobre películas, lo que les permitirá a los usuarios administrar sus películas favoritas y dejar o borrar opiniones.

b3d0ac1e181c9de9.png

Implementación de conectores

  1. Abre mutations.gql en dataconnect/movie-connector/.
  2. Agrega las siguientes mutaciones para administrar las películas favoritas:
# 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 })
}

Conclusiones clave:

  • userId_expr: "auth.uid": Usa auth.uid, que proporciona directamente Firebase Authentication, lo que garantiza que solo se acceda a los datos del usuario autenticado o se modifiquen.
  1. A continuación, abre queries.gql en dataconnect/movie-connector/.
  2. Agrega la siguiente consulta para verificar si una película está agregada a favoritos:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}

Conclusiones clave:

  • auth.uid: Garantiza un acceso seguro a los datos específicos del usuario con Firebase Authentication.
  • favorite_movie: Verifica la tabla de unión favorite_movies para ver si el usuario actual marcó una película específica como favorita.

Integración de consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de las siguientes importaciones:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. Reemplaza las funciones handleAddFavoritedMovie, handleDeleteFavoritedMovie y handleGetIfFavoritedMovie con el siguiente 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;
  }
};

Conclusiones clave:

  • handleAddFavoritedMovie y handleDeleteFavoritedMovie: Usa las mutaciones para agregar o quitar una película de los favoritos del usuario de forma segura.
  • handleGetIfFavoritedMovie: Usa la consulta getIfFavoritedMovie para verificar si el usuario marcó una película como favorita.

Descubre cómo funciona

Ahora, puedes marcar películas como favoritas o dejar de hacerlo haciendo clic en el ícono de corazón en las tarjetas de películas y en la página de detalles de la película. Además, puedes ver tus películas favoritas en la página de tu perfil.

Cómo implementar las opiniones de los usuarios

A continuación, implementarás la sección para administrar las opiniones de los usuarios en la app.

Implementación de conectores

  1. En mutations.gql (dataconnect/movie-connector/mutations.gql), agrega las siguientes mutaciones:
# 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 })
}

Conclusiones clave:

  • userId_expr: "auth.uid": Garantiza que las opiniones estén asociadas con el usuario autenticado.
  • reviewDate_date: { today: true }: Genera automáticamente la fecha actual de la revisión con DataConnect, lo que elimina la necesidad de ingresar datos de forma manual.

Integración de consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita la marca de comentario de las siguientes importaciones:
import { addReview, deleteReview } from "@movie/dataconnect";
  1. Reemplaza las funciones handleAddReview y handleDeleteReview con el siguiente 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;
  }
};

Conclusiones clave:

  • handleAddReview: Llama a la mutación addReview para agregar una opinión a la película especificada y vincularla de forma segura al usuario autenticado.
  • handleDeleteReview: Usa la mutación deleteReview para quitar una opinión de una película del usuario autenticado.

Observa cómo funciona

Los usuarios ahora pueden dejar opiniones sobre películas en la página de detalles correspondiente. También pueden ver y borrar sus opiniones en la página de su perfil, lo que les da control total sobre sus interacciones con la aplicación.

8. Filtros avanzados y concordancia de texto parcial

En esta sección, implementarás funciones de búsqueda avanzada, que les permiten a los usuarios buscar películas en función de un rango de calificaciones y años de estreno, filtrar por géneros y etiquetas, realizar coincidencias parciales de texto en títulos o descripciones, y hasta combinar varios filtros para obtener resultados más precisos.

ece70ee0ab964e28.png

Implementación de conectores

  1. Abrir queries.gql en dataconnect/movie-connector/.
  2. Agrega la siguiente consulta para admitir varias funciones de búsqueda:
# 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
    }
  }
}

Conclusiones clave:

  • Operador _and: Combina varias condiciones en una sola consulta, lo que permite que la búsqueda se filtre por varios campos, como releaseYear, rating y genre.
  • Operador contains: Busca coincidencias de texto parciales dentro de los campos. En esta consulta, busca coincidencias en title, description, name o reviewText.
  • Cláusula where: Especifica las condiciones para filtrar datos. Cada sección (películas, actores, reseñas) usa una cláusula where para definir los criterios específicos de la búsqueda.

Integración de consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita la marca de comentario de las siguientes importaciones:
import { searchAll, SearchAllData } from "@movie/dataconnect";
  1. Reemplaza la función handleSearchAll por el siguiente 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;
  }
};

Conclusiones clave:

  • handleSearchAll: Esta función usa la consulta searchAll para realizar una búsqueda basada en la entrada del usuario y filtrar los resultados por parámetros como año, calificación, género y coincidencias de texto parciales.

Observa cómo funciona

Dirígete a "Búsqueda avanzada" desde la barra de navegación de la aplicación web. Ahora puedes buscar películas, actores y opiniones con varios filtros y entradas, y así obtener resultados de la búsqueda detallados y personalizados.

9. Opcional: Implementa en Cloud (se requiere facturación)

Ahora que completaste la iteración de desarrollo local, es hora de implementar tu esquema, tus datos y tus consultas en el servidor. Esto se puede hacer con la extensión de VS Code de Firebase Data Connect o Firebase CLI.

Agrega una app web en Firebase console

  1. Crea una aplicación web en Firebase console y toma nota del ID de la aplicación.

7030822793e4d75b.png

  1. Para configurar una app web en Firebase console, haz clic en “Agregar app”. Por el momento, puedes ignorar de forma segura la configuración del SDK y la configuración, pero toma nota del objeto firebaseConfig generado.
  2. Reemplaza el firebaseConfig en 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. Compila la app web: En la carpeta app, usa Vite a fin de compilar la app web para la implementación de hosting:
cd app
npm run build

Configura Firebase Authentication en Console

  1. Configura Firebase Auth con el Acceso con Google

62af2f225e790ef6.png

  1. Permitir dominios para(Firebase Auth) [https://firebase.google.com/docs/auth/web/hosting] en la consola de tu proyecto (opcional) (p.ej., http://127.0.0.1):

c255098f12549886.png

Cómo implementar con Firebase CLI

  1. En dataconnect/dataconnect.yaml, asegúrate de que el ID de la instancia, la base de datos y el ID del servicio coincidan con tu proyecto:
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. Asegúrate de tener Firebase CLI configurado con tu proyecto.
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. En la terminal, ejecuta el siguiente comando para realizar la implementación:
firebase deploy --only dataconnect,hosting
  1. Ejecuta este comando para comparar los cambios de esquema:
firebase dataconnect:sql:diff
  1. Si los cambios son aceptables, aplícalos con lo siguiente:
firebase dataconnect:sql:migrate

Tu instancia de Cloud SQL para PostgreSQL se actualizará con el esquema y los datos implementados finales. Puedes supervisar el estado en Firebase console.

Ahora deberías poder ver la app en vivo en your-project.web.app/. Además, puedes hacer clic en Run (Production) en el panel de Firebase Data Connect, al igual que con los emuladores locales, para agregar datos al entorno de producción.

10. Opcional: Búsqueda de vectores con Firebase Data Connect

En esta sección, habilitarás la búsqueda de vectores en tu app de reseñas de películas con Firebase Data Connect. Esta función permite realizar búsquedas basadas en contenido, como encontrar películas con descripciones similares mediante incorporaciones vectoriales.

4b5aca5a447d2feb.png

Actualiza el esquema para incluir incorporaciones en un campo

  1. En dataconnect/schema/schema.gql, agrega el campo descriptionEmbedding a la tabla 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
}

Conclusión clave:

  • descriptionEmbedding: Vector @col(size:768): Este campo almacena las incorporaciones semánticas de las descripciones de películas, lo que permite la búsqueda de contenido basada en vectores en tu app.

Activa Vertex AI

  1. Sigue la guía de requisitos previos para configurar las APIs de Vertex AI con Google Cloud. Este paso es esencial para admitir la generación de incorporaciones y la funcionalidad de búsqueda vectorial.
  2. Vuelve a implementar tu esquema para activar pgvector y la búsqueda de vectores haciendo clic en Deploy to Production con la extensión VSCode de Firebase Data Connect.

Propaga la base de datos con incorporaciones

  1. Abre la carpeta dataconnect en VSCode y haz clic en Run(local) en optional_vector_embed.gql para propagar tu base de datos con incorporaciones para las películas.

b858da780f6ec103.png

Cómo agregar una búsqueda de vectores

  1. En dataconnect/movie-connector/queries.gql, agrega la siguiente consulta para realizar búsquedas de vectores:
# 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
  }
}

Conclusiones clave:

  • compare_embed: Especifica el modelo de incorporación (textembedding-gecko@003) y el texto de entrada ($query) para la comparación.
  • method: Especifica el método de similitud (L2), que representa la distancia euclidiana.
  • within: Limita la búsqueda a películas con una distancia de L2 de 2 o menos, y se enfoca en las coincidencias de contenido cercanas.
  • limit: Restringe la cantidad de resultados que se muestran a 5.

Cómo implementar la función de búsqueda de vectores en la app

  1. En app/src/lib/MovieService.ts, quita el comentario de las siguientes importaciones:

Cómo implementar la función de búsqueda de vectores en la app

Ahora que el esquema y la consulta están configurados, integra la búsqueda de vectores en la capa de servicio de tu app. Este paso te permite llamar a la búsqueda desde tu aplicación web.

En app/src/lib/ MovieService.ts, quita el comentario de las siguientes importaciones de los SDK. Esto funcionará como cualquier otra consulta.

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

Agrega la siguiente función para integrar la búsqueda basada en vectores en la 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;
  }
};


Conclusiones clave:

  • searchMoviesByDescription: Esta función llama a la consulta searchMovieDescriptionUsingL2similarity y pasa el texto de entrada para realizar una búsqueda de contenido basada en vectores.

Descubre cómo funciona

Navega a la sección "Búsqueda vectorial" en la barra de navegación y escribe frases como "romántica y moderna". Verás una lista de películas que coinciden con el contenido que estás buscando. También puedes ir a la página de detalles de cualquier película y consultar la sección de películas similares en la parte inferior de la página.

7b71f1c75633c1be.png

11. Conclusión

¡Felicitaciones! Deberías poder usar la app web. Si quieres jugar con tus propios datos de películas, no te preocupes. Para insertarlos con la extensión FDC, imita los archivos _insert.gql o agrégalos por medio del panel Data Connect Execution.

Más información