Créer avec Firebase Data Connect (Web)

1. Avant de commencer

Application FriendlyMovies

Dans cet atelier de programmation, vous allez intégrer Firebase Data Connect à une base de données Cloud SQL pour créer une application Web d'avis sur des films. L'application terminée montre comment Firebase Data Connect simplifie le processus de création d'applications basées sur SQL. Il inclut les fonctionnalités suivantes :

  • Authentification : implémentez une authentification personnalisée pour les requêtes et les mutations de votre application, en veillant à ce que seuls les utilisateurs autorisés puissent interagir avec vos données.
  • Schéma GraphQL : créez et gérez vos structures de données à l'aide d'un schéma GraphQL flexible adapté aux besoins d'une application Web d'avis sur des films.
  • Requêtes et mutations SQL : récupérez, mettez à jour et gérez les données dans Cloud SQL à l'aide de requêtes et de mutations optimisées par GraphQL.
  • Recherche avancée avec correspondance partielle des chaînes : utilisez les filtres et les options de recherche pour trouver des films en fonction de champs tels que le titre, la description ou les tags.
  • (Facultatif) Intégration de la recherche vectorielle : ajoutez une fonctionnalité de recherche de contenu à l'aide de la recherche vectorielle de Firebase Data Connect pour offrir une expérience utilisateur riche basée sur les entrées et les préférences.

Conditions préalables

Vous devez avoir des connaissances de base sur JavaScript.

Points abordés

  • Configurez Firebase Data Connect avec des émulateurs locaux.
  • Concevez un schéma de données à l'aide de Data Connect et de GraphQL.
  • Écrivez et testez différentes requêtes et mutations pour une application d'avis sur des films.
  • Découvrez comment Firebase Data Connect génère et utilise le SDK dans l'application.
  • Déployez votre schéma et gérez efficacement la base de données.

Prérequis

2. Configurer l'environnement de développement

Cette étape de l'atelier de programmation vous guidera dans la configuration de l'environnement pour commencer à créer votre application d'avis sur les films à l'aide de Firebase Data Connect.

  1. Clonez le dépôt du projet et installez les dépendances requises :
    git clone https://github.com/firebaseextended/codelab-dataconnect-web
    cd codelab-dataconnect-web
    cd ./app && npm i
    npm run dev
    
  2. Après avoir exécuté ces commandes, ouvrez http://localhost:5173 dans votre navigateur pour voir l'application Web s'exécuter en local. Il s'agit de votre interface utilisateur pour créer l'application d'avis sur les films et interagir avec ses fonctionnalités.93f6648a2532c606.png
  3. Ouvrez le dossier codelab-dataconnect-web cloné à l'aide de Visual Studio Code. C'est là que vous définirez votre schéma, écrirez des requêtes et testerez les fonctionnalités de l'application.
  4. Pour utiliser les fonctionnalités Data Connect, installez l'extension Firebase Data Connect pour Visual Studio.
    Vous pouvez également installer l'extension depuis Visual Studio Code Marketplace ou la rechercher dans VS Code.b03ee38c9a81b648.png
  5. Ouvrez ou créez un projet Firebase dans la console Firebase.
  6. Associez votre projet Firebase à l'extension Firebase Data Connect VSCode. Dans l'extension, procédez comme suit :
    1. Cliquez sur le bouton Se connecter.
    2. Cliquez sur Associer un projet Firebase, puis sélectionnez votre projet Firebase.
    4bb2fbf8f9fac29b.png
  7. Démarrez les émulateurs Firebase à l'aide de l'extension VS Code Firebase Data Connect :
    cliquez sur Start Emulators (Démarrer les émulateurs), puis vérifiez que les émulateurs sont en cours d'exécution dans le terminal.6d3d95f4cb708db1.png

3. Examiner le codebase de démarrage

Dans cette section, vous allez explorer les principales zones de la base de code de démarrage de l'application. Bien que l'application manque de certaines fonctionnalités, il est utile de comprendre sa structure globale.

Structure des dossiers et des fichiers

Les sous-sections suivantes présentent la structure des dossiers et des fichiers de l'application.

Le répertoire dataconnect/

Contient les configurations, les connecteurs (qui définissent les requêtes et les mutations) et les fichiers de schéma Firebase Data Connect.

  • schema/schema.gql : définit le schéma GraphQL.
  • connector/queries.gql : requêtes nécessaires dans votre application
  • connector/mutations.gql : mutations nécessaires dans votre application
  • connector/connector.yaml : fichier de configuration pour la génération du SDK

Le répertoire app/src/

Contient la logique de l'application et l'interaction avec Firebase Data Connect.

  • firebase.ts : configuration permettant de se connecter à une application Firebase dans votre projet Firebase.
  • lib/dataconnect-sdk/ : contient le SDK généré. Vous pouvez modifier l'emplacement de la génération du SDK dans le fichier connector/connector.yaml. Les SDK seront générés automatiquement chaque fois que vous définirez une requête ou une mutation.

4. Définir un schéma pour les critiques de films

Dans cette section, vous allez définir la structure et les relations entre les entités clés de l'application de films dans un schéma. Les entités telles que Movie, User, Actor et Review sont mappées aux tables de base de données, avec des relations établies à l'aide des directives de schéma Firebase Data Connect et GraphQL. Une fois en place, votre application sera prête à gérer toutes les opérations, de la recherche des films les mieux notés au filtrage par genre, en passant par la possibilité pour les utilisateurs de laisser des avis, de marquer des favoris, d'explorer des films similaires ou de trouver des films recommandés en fonction de la saisie de texte grâce à la recherche vectorielle.

Entités et relations principales

Le type Movie contient des informations clés telles que le titre, le genre et les tags, que l'application utilise pour les recherches et les profils de films. Le type User permet de suivre les interactions des utilisateurs, comme les avis et les favoris. Reviews associe les utilisateurs à des films, ce qui permet à l'application d'afficher les notes et les commentaires générés par les utilisateurs.

Les relations entre les films, les acteurs et les utilisateurs rendent l'application plus dynamique. La table de jointure MovieActor permet d'afficher les détails de la distribution et les filmographies des acteurs. Le type FavoriteMovie permet aux utilisateurs d'ajouter des films à leurs favoris. L'application peut ainsi afficher une liste de favoris personnalisée et mettre en avant les sélections populaires.

Configurer le tableau Movie

Le type Movie définit la structure principale d'une entité de film, y compris des champs tels que title, genre, releaseYear et rating.

Copiez et collez l'extrait de code dans votre fichier 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]
}

Points à retenir :

  • id : UUID unique pour chaque film, généré à l'aide de @default(expr: "uuidV4()").

Configurer le tableau MovieMetadata

Le type MovieMetadata établit une relation un-à-un avec le type Movie. Elle inclut des données supplémentaires, comme le réalisateur du film.

Copiez et collez l'extrait de code dans votre fichier 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
}

Points à retenir :

  • Film ! @ref: fait référence au type Movie, établissant une relation de clé étrangère.

Configurer le tableau Actor

Copiez et collez l'extrait de code dans votre fichier dataconnect/schema/schema.gql :

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

Le type Actor représente un acteur dans la base de données de films, où chaque acteur peut faire partie de plusieurs films, formant une relation plusieurs-à-plusieurs.

Configurer le tableau MovieActor

Copiez et collez l'extrait de code dans votre fichier 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"
}

Points à retenir :

  • movie : fait référence au type Movie et génère implicitement une clé étrangère movieId : UUID!.
  • actor : fait référence au type Actor et génère implicitement une clé étrangère actorId : UUID!.
  • role : définit le rôle de l'acteur dans le film (par exemple, "main" ou "supporting").

Configurer le tableau User

Le type User définit une entité utilisateur qui interagit avec les films en laissant des avis ou en les ajoutant à ses favoris.

Copiez et collez l'extrait de code dans votre fichier 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
}

Configurer le tableau FavoriteMovie

Le type FavoriteMovie est une table de jointure qui gère les relations plusieurs-à-plusieurs entre les utilisateurs et leurs films préférés. Chaque tableau associe un User à un Movie.

Copiez et collez l'extrait de code dans votre fichier 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!
}

Points à retenir :

  • movie : fait référence au type Movie et génère implicitement une clé étrangère movieId: UUID!.
  • user : fait référence au type d'utilisateur et génère implicitement une clé étrangère userId: UUID!.

Configurer le tableau Review

Le type Review représente l'entité d'avis et associe les types User et Movie dans une relation plusieurs-à-plusieurs (un utilisateur peut laisser plusieurs avis et chaque film peut avoir plusieurs avis).

Copiez et collez l'extrait de code dans votre fichier 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")
}

Points à retenir :

  • user : fait référence à l'utilisateur qui a laissé l'avis.
  • movie : fait référence au film en cours d'examen.
  • reviewDate : défini automatiquement sur l'heure à laquelle l'avis est créé à l'aide de @default(expr: "request.time").

Champs générés automatiquement et valeurs par défaut

Le schéma utilise des expressions telles que @default(expr: "uuidV4()") pour générer automatiquement des ID et des codes temporels uniques. Par exemple, le champ id des types Movie et Review est automatiquement renseigné avec un UUID lorsqu'un nouvel enregistrement est créé.

Maintenant que le schéma est défini, votre application de films dispose d'une base solide pour sa structure de données et ses relations.

5. Récupérer les films les plus populaires et les plus récents

Application FriendlyMovies

Dans cette section, vous allez insérer des données de films fictives dans les émulateurs locaux, puis implémenter les connecteurs (requêtes) et le code TypeScript pour appeler ces connecteurs dans l'application Web. À la fin, votre application sera en mesure d'extraire et d'afficher de manière dynamique les films les mieux notés et les plus récents directement depuis la base de données.

Insérer des données fictives sur des films, des acteurs et des avis

  1. Dans VSCode, ouvrez dataconnect/moviedata_insert.gql. Assurez-vous que les émulateurs de l'extension Firebase Data Connect sont en cours d'exécution.
  2. Un bouton Exécuter (local) devrait s'afficher en haut du fichier. Cliquez sur ce bouton pour insérer les données de film fictives dans votre base de données.
    e424f75e63bf2e10.png
  3. Consultez le terminal Exécution de Data Connect pour vérifier que les données ont bien été ajoutées.
    e0943d7704fb84ea.png

Implémenter le connecteur

  1. Ouvrez dataconnect/movie-connector/queries.gql. Vous trouverez une requête ListMovies de base dans les commentaires :
    query ListMovies @auth(level: PUBLIC) {
      movies {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
    Cette requête récupère tous les films et leurs détails (par exemple, id, title, releaseYear). Toutefois, elle ne trie pas les films.
  2. Remplacez la requête ListMovies existante par la requête suivante pour ajouter des options de tri et de limite :
    # List subset of fields for movies
    query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) {
      movies(
        orderBy: [
          { rating: $orderByRating },
          { releaseYear: $orderByReleaseYear }
        ]
        limit: $limit
      ) {
        id
        title
        imageUrl
        releaseYear
        genre
        rating
        tags
        description
      }
    }
    
  3. Cliquez sur le bouton Exécuter (local) pour exécuter la requête sur votre base de données locale. Vous pouvez également saisir les variables de requête dans le volet de configuration avant l'exécution.
    c4d947115bb11b16.png

Points à retenir :

  • movies() : champ de requête GraphQL permettant d'extraire les données de films de la base de données.
  • orderByRating : paramètre permettant de trier les films par note (croissant/décroissant).
  • orderByReleaseYear : paramètre permettant de trier les films par année de sortie (ordre croissant ou décroissant).
  • limit : limite le nombre de films renvoyés.

Intégrer des requêtes dans l'application Web

Dans cette partie de l'atelier de programmation, vous allez utiliser les requêtes définies dans la section précédente dans votre application Web. Les émulateurs Firebase Data Connect génèrent des SDK en fonction des informations contenues dans les fichiers .gql (plus précisément schema.gql, queries.gql et mutations.gql) et le fichier connector.yaml. Ces SDK peuvent être appelés directement dans votre application.

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez le commentaire de l'instruction d'importation en haut de la page :
    import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
    
    La fonction listMovies, le type de réponse ListMoviesData et l'enum OrderDirection sont tous des SDK générés par les émulateurs Firebase Data Connect en fonction du schéma et des requêtes que vous avez définis précédemment .
  2. Remplacez les fonctions handleGetTopMovies et handleGetLatestMovies par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • listMovies : fonction générée automatiquement qui appelle la requête listMovies pour récupérer une liste de films. Il inclut des options permettant de trier les résultats par note ou par année de sortie, et de limiter leur nombre.
  • ListMoviesData : type de résultat utilisé pour afficher les 10 films les plus populaires et les plus récents sur la page d'accueil de l'application.

Voir une démo

Actualisez votre application Web pour voir la requête en action. La page d'accueil affiche désormais de manière dynamique la liste des films, en récupérant les données directement depuis votre base de données locale. Les films les mieux notés et les plus récents s'affichent de manière fluide, reflétant les données que vous venez de configurer.

6. Afficher les détails d'un film et d'un acteur

Dans cette section, vous allez implémenter la fonctionnalité permettant de récupérer des informations détaillées sur un film ou un acteur à l'aide de leurs ID uniques. Cela implique non seulement d'extraire les données de leurs tables respectives, mais aussi de joindre les tables associées pour afficher des informations complètes, telles que les critiques de films et les filmographies des acteurs.

ac7fefa7ff779231.png

Implémenter des connecteurs

  1. Ouvrez dataconnect/movie-connector/queries.gql dans votre projet.
  2. Ajoutez les requêtes suivantes pour récupérer les informations sur les films et les acteurs :
    # 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. Enregistrez vos modifications et examinez les requêtes.

Points à retenir :

  • movie()/actor() : champs de requête GraphQL pour récupérer un seul film ou acteur à partir des tables Movies ou Actors.
  • _on_ : permet d'accéder directement aux champs d'un type associé qui présente une relation de clé étrangère. Par exemple, reviews_on_movie récupère tous les avis associés à un film spécifique.
  • _via_ : permet de parcourir les relations plusieurs-à-plusieurs via une table de jointure. Par exemple, actors_via_MovieActor accède au type Actor via la table de jointure MovieActor, et la condition where filtre les acteurs en fonction de leur rôle (par exemple, "principal" ou "secondaire").

Tester la requête en saisissant des données fictives

  1. Dans le volet d'exécution de Data Connect, vous pouvez tester la requête en saisissant des ID fictifs, par exemple :
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
    
  2. Cliquez sur Exécuter (local) pour GetMovieById afin de récupérer les informations sur "Quantum Paradox" (le faux film auquel l'ID ci-dessus se rapporte).

1b08961891e44da2.png

Intégrer des requêtes dans l'application Web

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez les commentaires des importations suivantes :
    import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
    import { GetActorByIdData, getActorById } from "@movie/dataconnect";
    
  2. Remplacez les fonctions handleGetMovieById et handleGetActorById par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • getMovieById / getActorById : il s'agit de fonctions générées automatiquement qui appellent les requêtes que vous avez définies, en récupérant des informations détaillées sur un film ou un acteur spécifique.
  • GetMovieByIdData / GetActorByIdData : il s'agit des types de résultats utilisés pour afficher les détails des films et des acteurs dans l'application.

Voir une démo

Accédez maintenant à la page d'accueil de votre application Web. Cliquez sur un film pour afficher tous ses détails, y compris les acteurs et les avis. Ces informations sont extraites des tables associées. De même, si vous cliquez sur un acteur, les films dans lesquels il a joué s'affichent.

7. Gérer l'authentification des utilisateurs

Dans cette section, vous allez implémenter les fonctionnalités de connexion et de déconnexion des utilisateurs à l'aide de Firebase Authentication. Vous utiliserez également les données Firebase Authentication pour récupérer ou insérer directement les données utilisateur dans Firebase DataConnect, ce qui garantira une gestion sécurisée des utilisateurs dans votre application.

9890838045d5a00e.png

Implémenter des connecteurs

  1. Ouvrez mutations.gql dans dataconnect/movie-connector/.
  2. Ajoutez la mutation suivante pour créer ou mettre à jour l'utilisateur authentifié actuel :
    # Create or update the current authenticated user
    mutation UpsertUser($username: String!) @auth(level: USER) {
      user_upsert(
        data: {
          id_expr: "auth.uid"
          username: $username
        }
      )
    }
    

Points à retenir :

  • id_expr: "auth.uid" : utilise auth.uid, qui est fourni directement par Firebase Authentication, et non par l'utilisateur ni l'application. Cela ajoute une couche de sécurité supplémentaire en garantissant que l'ID utilisateur est géré de manière sécurisée et automatique.

Récupérer l'utilisateur actuel

  1. Ouvrez queries.gql dans dataconnect/movie-connector/.
  2. Ajoutez la requête suivante pour récupérer l'utilisateur actuel :
    # 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
            }
          }
        }
      }
    }
    

Points à retenir :

  • auth.uid : cette valeur est récupérée directement à partir de Firebase Authentication, ce qui garantit un accès sécurisé aux données spécifiques à l'utilisateur.
  • Champs _on_ : ces champs représentent les tables de jointure :
    • reviews_on_user : récupère tous les avis associés à l'utilisateur, y compris les id et title du film.
    • favorite_movies_on_user : récupère tous les films marqués comme favoris par l'utilisateur, y compris des informations détaillées telles que genre, releaseYear, rating et metadata.

Intégrer des requêtes dans l'application Web

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez la mise en commentaire des importations suivantes :
    import { upsertUser } from "@movie/dataconnect";
    import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
    
  2. Remplacez les fonctions handleAuthStateChange et handleGetCurrentUser par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • handleAuthStateChange : cette fonction écoute les changements d'état de l'authentification. Lorsqu'un utilisateur se connecte, ses données sont définies et la mutation upsertUser est appelée pour créer ou mettre à jour ses informations dans la base de données.
  • handleGetCurrentUser : récupère le profil de l'utilisateur actuel à l'aide de la requête getCurrentUser, qui récupère les avis et les films favoris de l'utilisateur.

Voir une démo

Cliquez ensuite sur le bouton "Se connecter avec Google" dans la barre de navigation. Vous pouvez vous connecter à l'aide de l'émulateur Firebase Authentication. Une fois connecté, cliquez sur "Mon profil". Il sera vide pour le moment, mais vous avez jeté les bases de la gestion des données spécifiques aux utilisateurs dans votre application.

8. Implémenter les interactions utilisateur

Dans cette section de l'atelier de programmation, vous allez implémenter des interactions utilisateur dans l'application d'avis sur les films. Plus précisément, vous allez permettre aux utilisateurs de gérer leurs films préférés, et de laisser ou supprimer des avis.

b3d0ac1e181c9de9.png

Autoriser un utilisateur à ajouter un film à ses favoris

Dans cette section, vous allez configurer la base de données pour permettre aux utilisateurs d'ajouter des films à leurs favoris.

Implémenter des connecteurs

  1. Ouvrez mutations.gql dans dataconnect/movie-connector/.
  2. Ajoutez les mutations suivantes pour gérer les films favoris :
    # 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 })
    }
    
    

Points à retenir :

  • userId_expr: "auth.uid" : utilise auth.uid, qui est fourni directement par Firebase Authentication, ce qui garantit que seules les données de l'utilisateur authentifié sont accessibles ou modifiées.

Vérifier si un film a été ajouté aux favoris

  1. Ouvrez queries.gql dans dataconnect/movie-connector/.
  2. Ajoutez la requête suivante pour vérifier si un film a été ajouté aux favoris :
    query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
        movieId
      }
    }
    

Points à retenir :

  • auth.uid : assure un accès sécurisé aux données spécifiques à l'utilisateur à l'aide de Firebase Authentication.
  • favorite_movie : vérifie si un film spécifique est marqué comme favori par l'utilisateur actuel dans la table de jointure favorite_movies.

Intégrer des requêtes dans l'application Web

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez les commentaires des importations suivantes :
    import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
    
  2. Remplacez les fonctions handleAddFavoritedMovie, handleDeleteFavoritedMovie et handleGetIfFavoritedMovie par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • handleAddFavoritedMovie et handleDeleteFavoritedMovie : utilisez les mutations pour ajouter ou supprimer un film des favoris de l'utilisateur de manière sécurisée.
  • handleGetIfFavoritedMovie : utilise la requête getIfFavoritedMovie pour vérifier si un film est marqué comme favori par l'utilisateur.

Voir une démo

Vous pouvez désormais ajouter ou supprimer des films de vos favoris en cliquant sur l'icône en forme de cœur sur les fiches et la page d'informations des films. Vous pouvez également consulter vos films préférés sur la page de votre profil.

Autoriser les utilisateurs à laisser ou supprimer des avis

Ensuite, vous allez implémenter la section permettant de gérer les avis des utilisateurs dans l'application.

Implémenter des connecteurs

Dans mutations.gql (dataconnect/movie-connector/mutations.gql), ajoutez les mutations suivantes :

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

Points à retenir :

  • userId_expr: "auth.uid" : permet de s'assurer que les avis sont associés à l'utilisateur authentifié.
  • reviewDate_date: { today: true } : génère automatiquement la date actuelle de l'avis à l'aide de DataConnect, ce qui évite de la saisir manuellement.

Intégrer des requêtes dans l'application Web

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez les commentaires des importations suivantes :
    import { addReview, deleteReview } from "@movie/dataconnect";
    
  2. Remplacez les fonctions handleAddReview et handleDeleteReview par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • handleAddReview : appelle la mutation addReview pour ajouter un avis sur le film spécifié, en l'associant de manière sécurisée à l'utilisateur authentifié.
  • handleDeleteReview : utilise la mutation deleteReview pour supprimer un avis sur un film par l'utilisateur authentifié.

Voir une démo

Les utilisateurs peuvent désormais laisser des avis sur les films sur la page d'informations correspondante. Ils peuvent également consulter et supprimer leurs avis sur la page de leur profil, ce qui leur donne un contrôle total sur leurs interactions avec l'application.

9. Filtres avancés et correspondance partielle du texte

Dans cette section, vous allez implémenter des fonctionnalités de recherche avancées, permettant aux utilisateurs de rechercher des films en fonction d'une plage de classifications et d'années de sortie, de filtrer par genres et tags, d'effectuer une correspondance partielle du texte dans les titres ou les descriptions, et même de combiner plusieurs filtres pour obtenir des résultats plus précis.

ece70ee0ab964e28.png

Implémenter des connecteurs

  1. Ouvrez queries.gql dans dataconnect/movie-connector/.
  2. Ajoutez la requête suivante pour prendre en charge différentes fonctionnalités de recherche :
    # 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
        }
      }
    }
    

Points à retenir :

  • Opérateur _and : combine plusieurs conditions dans une même requête, ce qui permet de filtrer la recherche par plusieurs champs tels que releaseYear, rating et genre.
  • Opérateur contains : recherche les correspondances textuelles partielles dans les champs. Dans cette requête, il recherche des correspondances dans title, description, name ou reviewText.
  • Clause where : spécifie les conditions de filtrage des données. Chaque section (films, acteurs, avis) utilise une clause where pour définir les critères spécifiques de la recherche.

Intégrer des requêtes dans l'application Web

  1. Dans MovieService (app/src/lib/MovieService.tsx), supprimez les commentaires des importations suivantes :
    import { searchAll, SearchAllData } from "@movie/dataconnect";
    
  2. Remplacez la fonction handleSearchAll par le code suivant :
    // 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;
      }
    };
    

Points à retenir :

  • handleSearchAll : cette fonction utilise la requête searchAll pour effectuer une recherche en fonction de la saisie de l'utilisateur, en filtrant les résultats par paramètres tels que l'année, la note, le genre et les correspondances partielles de texte.

Voir une démo

Accédez à la page "Recherche avancée" depuis la barre de navigation de l'application Web. Vous pouvez désormais rechercher des films, des acteurs et des critiques à l'aide de différents filtres et entrées, et obtenir des résultats de recherche détaillés et personnalisés.

10. Facultatif : Déployer sur le cloud (facturation requise)

Maintenant que vous avez effectué l'itération de développement local, il est temps de déployer votre schéma, vos données et vos requêtes sur le serveur. Pour ce faire, vous pouvez utiliser l'extension VS Code Firebase Data Connect ou la CLI Firebase.

Mettre à niveau votre forfait Firebase

Pour intégrer Firebase Data Connect à Cloud SQL pour PostgreSQL, votre projet Firebase doit être associé au forfait Blaze avec paiement à l'usage, ce qui signifie qu'il est associé à un compte de facturation Cloud.

  • Un compte de facturation Cloud nécessite un mode de paiement, comme une carte de crédit.
  • Si vous débutez avec Firebase et Google Cloud, vérifiez si vous êtes éligible à un crédit de 300$et à un compte de facturation Cloud pour l'essai sans frais.
  • Si vous effectuez cet atelier de programmation dans le cadre d'un événement, demandez à l'organisateur si des crédits Cloud sont disponibles.

Pour passer à la formule Blaze, procédez comme suit :

  1. Dans la console Firebase, sélectionnez Passer à une formule supérieure.
  2. Sélectionnez le forfait Blaze. Suivez les instructions à l'écran pour associer un compte de facturation Cloud à votre projet.
    Si vous avez dû créer un compte de facturation Cloud lors de cette mise à niveau, vous devrez peut-être revenir au processus de mise à niveau dans la console Firebase pour la finaliser.

Associez votre application Web à votre projet Firebase.

  1. Enregistrez votre application Web dans votre projet Firebase à l'aide de la console Firebase :
    1. Ouvrez votre projet, puis cliquez sur Add App (Ajouter une application).
    2. Ignorez pour l'instant la configuration du SDK, mais veillez à copier l'objet firebaseConfig généré.
    7030822793e4d75b.png
  2. Remplacez le firebaseConfig existant dans app/src/lib/firebase.tsx par la configuration que vous venez de copier depuis la console Firebase.
    const firebaseConfig = {
      apiKey: "API_KEY",
      authDomain: "PROJECT_ID.firebaseapp.com",
      projectId: "PROJECT_ID",
      storageBucket: "PROJECT_ID.firebasestorage.app",
      messagingSenderId: "SENDER_ID",
      appId: "APP_ID"
    };
    
  3. Créez l'application Web : de retour dans VS Code, dans le dossier app, utilisez Vite pour créer l'application Web en vue du déploiement de l'hébergement :
    cd app
    npm run build
    

Configurer Firebase Authentication dans votre projet Firebase

  1. Configurez Firebase Authentication avec Google Sign-In.62af2f225e790ef6.png
  2. (Facultatif) Autorisez les domaines pour Firebase Authentication à l'aide de la console Firebase (par exemple, http://127.0.0.1).
    1. Dans les paramètres Authentification, accédez à Domaines autorisés.
    2. Cliquez sur "Ajouter un domaine" et ajoutez votre domaine local à la liste.

c255098f12549886.png

Déployer avec la CLI Firebase

  1. Dans dataconnect/dataconnect.yaml, assurez-vous que l'ID de votre instance, de votre base de données et de votre service correspondent à votre projet :
    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. Assurez-vous que la CLI Firebase est configurée avec votre projet :
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
    
  3. Dans votre terminal, exécutez la commande suivante pour déployer :
    firebase deploy --only dataconnect,hosting
    
  4. Exécutez cette commande pour comparer les modifications apportées à votre schéma :
    firebase dataconnect:sql:diff
    
  5. Si les modifications sont acceptables, appliquez-les avec :
    firebase dataconnect:sql:migrate
    

Votre instance Cloud SQL pour PostgreSQL sera mise à jour avec le schéma et les données déployés finaux. Vous pouvez surveiller l'état dans la console Firebase.

Vous devriez maintenant pouvoir voir votre application en direct sur your-project.web.app/. Vous pouvez également cliquer sur Exécuter (production) dans le panneau Firebase Data Connect, comme vous l'avez fait avec les émulateurs locaux, pour ajouter des données à l'environnement de production.

11. Facultatif : Recherche vectorielle avec Firebase Data Connect (facturation requise)

Dans cette section, vous allez activer la recherche vectorielle dans votre application d'avis sur les films à l'aide de Firebase Data Connect. Cette fonctionnalité permet d'effectuer des recherches basées sur le contenu, par exemple pour trouver des films avec des descriptions similaires à l'aide d'embeddings vectoriels.

Pour cette étape, vous devez avoir terminé la dernière étape de cet atelier de programmation pour déployer sur Google Cloud.

4b5aca5a447d2feb.png

Mettre à jour le schéma pour inclure des embeddings pour un champ

Dans dataconnect/schema/schema.gql, ajoutez le champ descriptionEmbedding à la table 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
}

Points à retenir :

  • descriptionEmbedding: Vector @col(size:768) : ce champ stocke les embeddings sémantiques des descriptions de films, ce qui permet la recherche de contenu basée sur des vecteurs dans votre application.

Activer Vertex AI

  1. Suivez le guide des prérequis pour configurer les API Vertex AI depuis Google Cloud. Cette étape est essentielle pour prendre en charge la génération d'embeddings et la fonctionnalité de recherche vectorielle.
  2. Redéployez votre schéma pour activer pgvector et la recherche vectorielle en cliquant sur "Deploy to Production" (Déployer en production) à l'aide de l'extension VS Code Firebase Data Connect.

Insérer des représentations vectorielles continues dans la base de données

  1. Ouvrez le dossier dataconnect dans VS Code.
  2. Cliquez sur Run(local) (Exécuter (local)) dans optional_vector_embed.gql pour remplir votre base de données avec les embeddings des films.

b858da780f6ec103.png

Ajouter une requête Vector Search

Dans dataconnect/movie-connector/queries.gql, ajoutez la requête suivante pour effectuer des recherches vectorielles :

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

Points à retenir :

  • compare_embed : spécifie le modèle d'embedding (textembedding-gecko@003) et le texte d'entrée ($query) à comparer.
  • method : spécifie la méthode de similarité (L2), qui représente la distance euclidienne.
  • within : limite la recherche aux films dont la distance L2 est inférieure ou égale à 2, en se concentrant sur les contenus proches.
  • limit : limite le nombre de résultats renvoyés à cinq.

Implémenter la fonction de recherche vectorielle dans votre application

Maintenant que le schéma et la requête sont configurés, intégrez la recherche vectorielle à la couche de service de votre application. Cette étape vous permet d'appeler la requête de recherche depuis votre application Web.

  1. Dans app/src/lib/ MovieService.ts, décommentez les importations suivantes à partir des SDK. Cela fonctionnera comme n'importe quelle autre requête.
    import {
      searchMovieDescriptionUsingL2similarity,
      SearchMovieDescriptionUsingL2similarityData,
    } from "@movie/dataconnect";
    
  2. Ajoutez la fonction suivante pour intégrer la recherche vectorielle à l'application :
    // 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;
      }
    };
    

Points à retenir :

  • searchMoviesByDescription : cette fonction appelle la requête searchMovieDescriptionUsingL2similarity, en transmettant le texte d'entrée pour effectuer une recherche de contenu basée sur des vecteurs.

Voir une démo

Accédez à la section "Vector Search" (Recherche vectorielle) dans la barre de navigation, puis saisissez des expressions telles que "romantique et moderne". Vous verrez une liste de films correspondant au contenu que vous recherchez. Vous pouvez également accéder à la page d'informations d'un film et consulter la section "Films similaires" en bas de la page.

7b71f1c75633c1be.png

12. Conclusion

Félicitations, vous devriez pouvoir utiliser l'application Web ! Si vous souhaitez utiliser vos propres données de films, ne vous inquiétez pas. Insérez vos propres données à l'aide de l'extension Firebase Data Connect en imitant les fichiers _insert.gql ou ajoutez-les via le volet d'exécution Data Connect dans VS Code.

En savoir plus