Compila con Firebase Data Connect (web)

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 una autenticación personalizada para las consultas y mutaciones de tu app, y asegúrate de 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 y adaptado a las necesidades de una app web de reseñas de películas.
  • Consultas y mutaciones de SQL: Recupera, actualiza y administra datos en Cloud SQL con consultas y mutaciones potenciadas por 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.
  • (Opcional) Integración de la búsqueda de vectores: Agrega la función de búsqueda de contenido con la búsqueda de vectores de Firebase Data Connect para brindar una experiencia del usuario enriquecida basada en las entradas y las preferencias.

Requisitos previos

Necesitarás conocimientos básicos de JavaScript.

Qué aprenderás

  • Configura Firebase Data Connect con emuladores locales.
  • Diseña un esquema de datos con Data Connect y GraphQL.
  • Escribir y probar varias consultas y mutaciones para una app de opiniones de películas
  • Obtén más 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 manera eficiente.

Requisitos

2. Configura tu entorno de desarrollo

En esta etapa del codelab, se te guiará para que configures el entorno y comiences a compilar tu app de reseñas de películas con Firebase Data Connect.

  1. Clona 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
    
  2. Después de ejecutar estos comandos, abre http://localhost:5173 en tu navegador para ver la app web que se ejecuta de forma local. Este es tu frontend para compilar la app de reseñas de películas y para interactuar con sus funciones.93f6648a2532c606.png
  3. Abre la carpeta codelab-dataconnect-web clonada con Visual Studio Code. Aquí definirás tu esquema, escribirás consultas y probarás la funcionalidad de la app.
  4. Para usar las funciones de Data Connect, instala la extensión de Firebase Data Connect para Visual Studio.
    También puedes instalar la extensión desde Visual Studio Code Marketplace o buscarla en VS Code.b03ee38c9a81b648.png
  5. Abre o crea un proyecto nuevo de Firebase en Firebase console.
  6. Conecta tu proyecto de Firebase a la extensión de Firebase Data Connect de VS Code. En la extensión, haz lo siguiente:
    1. Haz clic en el botón Acceder.
    2. Haz clic en Conectar un proyecto de Firebase y selecciona tu proyecto de Firebase.
    4bb2fbf8f9fac29b.png
  7. Inicia los emuladores de Firebase con la extensión de Firebase Data Connect para VS Code:
    Haz clic en Start Emulators y, luego, confirma que los emuladores se estén ejecutando en la terminal.6d3d95f4cb708db1.png

3. Revisa la base de código de inicio

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

Estructura de carpetas y archivos

En las siguientes subsecciones, se proporciona una descripción general de la estructura de carpetas y archivos de la app.

El directorio dataconnect/

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

  • schema/schema.gql: Define el esquema de GraphQL
  • connector/queries.gql: Consultas necesarias en tu app
  • connector/mutations.gql: Mutaciones necesarias en tu app
  • connector/connector.yaml: Archivo de configuración para la generación del SDK

El directorio app/src/

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

  • firebase.ts: Es la configuración para conectarse a una app de Firebase en tu proyecto de Firebase.
  • lib/dataconnect-sdk/: Contiene el SDK generado. Puedes editar la ubicación de la generación del SDK en el archivo connector/connector.yaml, y los SDKs se generarán automáticamente cada vez que definas una consulta o una mutación.

4. Cómo definir un esquema para las opiniones sobre películas

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 a través de Firebase Data Connect y directivas de esquema de GraphQL. Una vez que esté en su lugar, tu app estará lista para controlar todo, desde buscar películas mejor calificadas y filtrar por género hasta permitir que los usuarios dejen opiniones, marquen favoritos, exploren películas similares o encuentren películas recomendadas basadas en 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 las búsquedas y los perfiles de películas. El tipo User hace un seguimiento de las interacciones del usuario, como las opiniones y los favoritos. Reviews conectar 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 agregar películas a sus favoritos, de modo que la app pueda mostrar una lista de favoritos personalizada y destacar las selecciones populares.

Configura la tabla Movie

El tipo Movie define la estructura principal de una entidad de película, 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()").

Configura la 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, lo que establece una relación de clave externa.

Configura la tabla Actor

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 formar parte de varias películas, lo que genera una relación de muchos a muchos.

Configura la 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 Movie y genera de forma implícita una clave externa movieId: UUID!.
  • actor: Hace referencia al tipo Actor y genera de forma implícita una clave externa actorId: UUID!.
  • role: Define el rol del actor en la película (p.ej., "principal" o "secundaria").

Configura la tabla User

El tipo User define una entidad de usuario que interactúa con películas dejando opiniones o agregándolas a favoritos.

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

Configura la tabla FavoriteMovie

El tipo FavoriteMovie es una tabla de unión que controla las relaciones de muchos a muchos entre los usuarios y sus películas favoritas. 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 y genera de forma implícita una clave externa movieId: UUID!.
  • user: Hace referencia al tipo de usuario y genera de forma implícita una clave externa userId: UUID!.

Configura la tabla Review

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

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 fecha y hora en que se crea la opinión con @default(expr: "request.time").

Campos y valores predeterminados 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 Movie y Review se propaga 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.

5. Recupera las películas más recientes y populares

App de FriendlyMovies

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

Inserta datos simulados de películas, actores y opiniones

  1. En VSCode, abre dataconnect/moviedata_insert.gql. Asegúrate de que los emuladores de la extensión de Firebase Data Connect se estén ejecutando.
  2. Deberías ver un botón Ejecutar (local) en la parte superior del archivo. Haz clic aquí para insertar los datos simulados de películas en tu base de datos.
    e424f75e63bf2e10.png
  3. Verifica la terminal de Ejecución de Data Connect para confirmar que los datos se agregaron correctamente.
    e0943d7704fb84ea.png

Implementa el conector

  1. Abre dataconnect/movie-connector/queries.gql. Encontrarás una consulta básica de ListMovies 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 (por ejemplo, id, title, releaseYear). Sin embargo, no ordena las películas.
  2. Reemplaza la consulta ListMovies existente 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
      }
    }
    
  3. 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 ejecutar la consulta.
    c4d947115bb11b16.png

Conclusiones clave:

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

Integra consultas en la app web

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

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de la declaración import 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 generados por los emuladores de Firebase Data Connect en función del esquema y las consultas que definiste anteriormente .
  2. 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 búsqueda de listMovies para recuperar una lista de películas. Incluye opciones para ordenar por calificación o año de lanzamiento, y limitar la cantidad de resultados.
  • ListMoviesData: Es el tipo de resultado que se usa para mostrar las 10 películas más populares y las más recientes en la página principal de la app.

Vea cómo funciona

Vuelve a cargar tu app web para ver la consulta en acción. Ahora, la página principal muestra de forma dinámica la lista de películas, ya que recupera los datos directamente de tu base de datos local. Verás que las películas mejor calificadas y más recientes aparecen sin problemas, lo que refleja los datos que acabas de configurar.

6. Mostrar detalles de películas y actores

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

ac7fefa7ff779231.png

Implementa conectores

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

Conclusiones clave:

  • movie()/actor(): Campos de consulta de GraphQL para recuperar una sola película o actor de la tabla Movies o Actors
  • _on_: 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 Actor a través de la tabla de unión MovieActor, y la condición where filtra a los actores según su rol (por ejemplo, "principal" o "secundario").

Prueba la consulta ingresando datos simulados

  1. En el panel de ejecución de Data Connect, puedes probar la consulta ingresando IDs simulados, como los siguientes:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Haz clic en Ejecutar (local) para GetMovieById y recuperar los detalles de "Quantum Paradox" (la película simulada a la que se relaciona el ID anterior).

1b08961891e44da2.png

Integra consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de las siguientes importaciones:
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. 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 y getActorById: Son funciones generadas automáticamente que llaman a las búsquedas que definiste y recuperan información detallada sobre una película o un actor específicos.
  • GetMovieByIdData y GetActorByIdData: Estos son los tipos de resultados que se usan para mostrar los detalles de películas y actores en la app.

Vea cómo funciona

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

7. Controla la autenticación del usuario

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

9890838045d5a00e.png

Implementa conectores

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

Conclusiones clave:

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

Cómo recuperar el usuario actual

  1. Abrir queries.gql en dataconnect/movie-connector/
  2. 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 clave:

  • auth.uid: Se recupera directamente de 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, incluidos los elementos id y title de la película.
    • favorite_movies_on_user: Recupera todas las películas marcadas como favoritas por el usuario, incluida información detallada como genre, releaseYear, rating y metadata.

Integra consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita la marca de comentario de las siguientes importaciones:
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. 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 los cambios en el estado de autenticación. Cuando un usuario accede, se configuran sus datos y se llama a la mutación upsertUser para crear o actualizar su información en la base de datos.
  • handleGetCurrentUser: Recupera el perfil del usuario actual con la consulta getCurrentUser, que recupera las opiniones del usuario y sus películas favoritas.

Vea cómo funciona

Ahora, haz clic en el botón "Acceder con Google" en la barra de navegación. Puedes acceder con el emulador de Firebase Authentication. Después de acceder, haz clic en "Mi perfil". Por ahora, estará vacío, pero ya configuraste la base para el manejo de datos específicos del usuario en tu app.

8. Implementa interacciones del usuario

En esta sección del codelab, implementarás interacciones del usuario en la app de opiniones sobre películas, específicamente permitirás que los usuarios administren sus películas favoritas y dejen o borren opiniones.

b3d0ac1e181c9de9.png

Permite que un usuario marque una película como favorita

En esta sección, configurarás la base de datos para que los usuarios puedan agregar películas a sus favoritos.

Implementa conectores

  1. Abrir mutations.gql en dataconnect/movie-connector/
  2. Agrega las siguientes mutaciones para controlar la acción de agregar películas a 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 })
    }
    
    

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.

Cómo verificar si una película está marcada como favorita

  1. Abrir queries.gql en dataconnect/movie-connector/
  2. Agrega la siguiente consulta para verificar si una película se marcó como favorita:
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

Conclusiones clave:

  • auth.uid: Garantiza el 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.

Integra 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";
    
  2. Reemplaza las funciones handleAddFavoritedMovie, handleDeleteFavoritedMovie y handleGetIfFavoritedMovie por 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.

Vea cómo funciona

Ahora puedes agregar películas a tus favoritos o quitarlas de ellos haciendo clic en el ícono de corazón de las tarjetas de películas y la página de detalles de la película. Además, puedes ver tus películas favoritas en tu página de perfil.

Permite que los usuarios dejen o borren opiniones

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

Implementa conectores

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.

Integra consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de las siguientes importaciones:
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. Reemplaza las funciones handleAddReview y handleDeleteReview por 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 revisión de la película especificada y vincularla de forma segura al usuario autenticado.
  • handleDeleteReview: Usa la mutación deleteReview para quitar una revisión de una película del usuario autenticado.

Vea cómo funciona

Ahora los usuarios pueden dejar opiniones sobre las películas en la página de detalles de la película. También pueden ver y borrar sus opiniones en su página de perfil, lo que les brinda control total sobre sus interacciones con la app.

9. Filtros avanzados y concordancia parcial de texto

En esta sección, implementarás capacidades de búsqueda avanzadas, lo que permitirá a los usuarios buscar películas según un rango de calificaciones y años de lanzamiento, 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

Implementa conectores

  1. Abre queries.gql en dataconnect/movie-connector/.
  2. Agrega la siguiente consulta para admitir varias capacidades 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 búsqueda, lo que permite filtrar la búsqueda por varios campos, como releaseYear, rating y genre.
  • Operador contains: Busca coincidencias parciales de texto dentro de los campos. En esta búsqueda, se buscan coincidencias dentro de title, description, name o reviewText.
  • Cláusula where: Especifica las condiciones para filtrar los datos. Cada sección (películas, actores, opiniones) usa una cláusula where para definir los criterios específicos de la búsqueda.

Integra consultas en la app web

  1. En MovieService (app/src/lib/MovieService.tsx), quita el comentario de las siguientes importaciones:
    import { searchAll, SearchAllData } from "@movie/dataconnect";
    
  2. 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 búsqueda searchAll para realizar una búsqueda basada en la entrada del usuario y filtra los resultados por parámetros como año, calificación, género y coincidencias parciales de texto.

Vea cómo funciona

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

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

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

Actualiza tu plan de precios de Firebase

Para integrar Firebase Data Connect con Cloud SQL para PostgreSQL, tu proyecto de Firebase debe tener el plan de precios de pago por uso (Blaze), lo que significa que está vinculado a una cuenta de Facturación de Cloud.

  • Una cuenta de facturación de Cloud requiere una forma de pago, como una tarjeta de crédito.
  • Si es la primera vez que usas Firebase y Google Cloud, verifica si cumples con los requisitos para obtener un crédito de USD 300 y una cuenta de Facturación de Cloud de prueba gratuita.
  • Si realizas este codelab como parte de un evento, pregúntale al organizador si hay créditos de Cloud disponibles.

Para actualizar tu proyecto al plan Blaze, sigue estos pasos:

  1. En Firebase console, selecciona la opción para actualizar tu plan.
  2. Selecciona el plan Blaze. Sigue las instrucciones en pantalla para vincular una cuenta de Facturación de Cloud a tu proyecto.
    Si necesitas crear una cuenta de Facturación de Cloud como parte de esta actualización, es posible que debas volver al flujo de actualización en Firebase console para completar la actualización.

Conecta tu app web a tu proyecto de Firebase

  1. Registra tu app web en tu proyecto de Firebase con Firebase console:
    1. Abre tu proyecto y, luego, haz clic en Add App.
    2. Por el momento, ignora la configuración del SDK, pero asegúrate de copiar el objeto firebaseConfig generado.
    7030822793e4d75b.png
  2. Reemplaza el firebaseConfig existente en app/src/lib/firebase.tsx por la configuración que acabas de copiar de Firebase console.
    const firebaseConfig = {
      apiKey: "API_KEY",
      authDomain: "PROJECT_ID.firebaseapp.com",
      projectId: "PROJECT_ID",
      storageBucket: "PROJECT_ID.firebasestorage.app",
      messagingSenderId: "SENDER_ID",
      appId: "APP_ID"
    };
    
  3. Compila la app web: De vuelta en VS Code, en la carpeta app, usa Vite para compilar la app web para la implementación de hosting:
    cd app
    npm run build
    

Configura Firebase Authentication en tu proyecto de Firebase

  1. Configura Firebase Authentication con Acceso con Google.62af2f225e790ef6.png
  2. (Opcional) Permite dominios para Firebase Authentication con Firebase console (por ejemplo, http://127.0.0.1).
    1. En la configuración de Authentication, ve a Authorized Domains.
    2. Haz clic en "Agregar dominio" e incluye tu dominio local en la lista.

c255098f12549886.png

Implementa 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"]
    
  2. Asegúrate de tener configurada Firebase CLI con tu proyecto:
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
    
  3. En tu terminal, ejecuta el siguiente comando para realizar la implementación:
    firebase deploy --only dataconnect,hosting
    
  4. Ejecuta este comando para comparar los cambios del esquema:
    firebase dataconnect:sql:diff
    
  5. Si los cambios son aceptables, aplícalos con el siguiente comando:
    firebase dataconnect:sql:migrate
    

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

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

11. Opcional: Búsqueda de vectores con Firebase Data Connect (se requiere facturación)

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

Para este paso, debes haber completado el último paso de este codelab para realizar la implementación en Google Cloud.

4b5aca5a447d2feb.png

Actualiza el esquema para incluir las incorporaciones de un campo

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
}

Conclusiones clave:

  • descriptionEmbedding: Vector @col(size:768): Este campo almacena los embeddings semánticos 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 desde Google Cloud. Este paso es fundamental para admitir la generación de incorporaciones y la funcionalidad de Vector Search.
  2. Vuelve a implementar tu esquema para activar pgvector y la búsqueda de vectores. Para ello, haz clic en "Implementar en producción" con la extensión de Firebase Data Connect para VS Code.

Completa la base de datos con incorporaciones

  1. Abre la carpeta dataconnect en VS Code.
  2. Haz clic en Run(local) en optional_vector_embed.gql para propagar tu base de datos con las incorporaciones de las películas.

b858da780f6ec103.png

Cómo agregar una consulta de búsqueda de vectores

En dataconnect/movie-connector/queries.gql, agrega la siguiente búsqueda para realizar búsquedas vectoriales:

# 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 L2 de 2 o menos, y se enfoca en coincidencias de contenido cercanas.
  • limit: Restringe la cantidad de resultados que se muestran a 5.

Implementa la función de búsqueda vectorial en tu 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 app web.

  1. En app/src/lib/ MovieService.ts, quita el comentario de las siguientes importaciones de los SDKs. Esto funcionará como cualquier otra consulta.
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. 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.

Vea cómo funciona

Navega a la sección "Vector Search" en la barra de navegación y escribe frases como "romántico y moderno". Verás una lista de películas que coinciden con el contenido que buscas o, si vas a la página de detalles de cualquier película, podrás consultar la sección de películas similares en la parte inferior de la página.

7b71f1c75633c1be.png

12. Conclusión

¡Felicitaciones! Deberías poder usar la app web. Si quieres experimentar con tus propios datos de películas, no te preocupes. Inserta tus propios datos con la extensión de Firebase Data Connect imitando los archivos _insert.gql o agrégalos a través del panel de ejecución de Data Connect en VS Code.

Más información