פיתוח באמצעות Firebase Data Connect

1. לפני שתתחיל

אפליקציית FriendlyMovies

בקודלאב הזה תלמדו לשלב את Firebase Data Connect עם מסד נתונים של Cloud SQL כדי ליצור אפליקציית אינטרנט של ביקורות על סרטים. האפליקציה המושלמת תמחיש איך Firebase Data Connect מפשט את תהליך היצירה של אפליקציות שמבוססות על SQL. התכונות האלה כלולות בו:

  • אימות: מטמיעים אימות מותאם אישית לשאילתות ולמוטציות של האפליקציה, כדי לוודא שרק משתמשים מורשים יכולים ליצור אינטראקציה עם הנתונים.
  • סכימת GraphQL: יצירת וניהול של מבני הנתונים באמצעות סכימה גמישה של GraphQL שמותאמת לצרכים של אפליקציית אינטרנט לביקורות על סרטים.
  • שאילתות וטרנספורמציות של SQL: אחזור, עדכון וניהול של נתונים ב-Cloud SQL באמצעות שאילתות וטרנספורמציות שמבוססות על GraphQL.
  • חיפוש מתקדם עם התאמה חלקית למחרוזת: אפשר להשתמש במסננים ובאפשרויות חיפוש כדי למצוא סרטים על סמך שדות כמו שם, תיאור או תגים.
  • אופציונלי: שילוב של חיפוש וקטורים: מוסיפים פונקציונליות של חיפוש תוכן באמצעות חיפוש וקטורים של Firebase Data Connect, כדי לספק חוויית משתמש עשירה על סמך קלט והעדפות.

דרישות מוקדמות

נדרשת הבנה בסיסית של JavaScript.

מה תלמדו

  • הגדרת Firebase Data Connect באמצעות אמולטורים מקומיים.
  • עיצוב סכימת נתונים באמצעות Data Connect ו-GraphQL.
  • כתיבת בדיקה של שאילתות ומוטציות שונות לאפליקציה של ביקורות על סרטים.
  • איך Firebase Data Connect יוצר את ה-SDK ומשתמש בו באפליקציה
  • פריסת הסכימה וניהול יעיל של מסד הנתונים.

מה צריך?

  • Git
  • Visual Studio Code
  • התקנה של Node.js באמצעות nvm-windows (ב-Windows) או nvm (ב-macOS או ב-Linux)
  • אם עדיין לא עשיתם זאת, יוצרים פרויקט Firebase במסוף Firebase.
  • (אופציונלי) כדי לבצע חיפוש וקטורים, צריך לשדרג את הפרויקט לתוכנית Blaze

הגדרת סביבת הפיתוח

בקטע הזה נסביר איך מגדירים את הסביבה כדי להתחיל לפתח את אפליקציית ביקורות הסרטים באמצעות Firebase Data Connect.

שלב 1: משכפלים את מאגר הפרויקט

מתחילים בהעתקה (cloning) של מאגר הפרויקט ובהתקנה של יחסי התלות הנדרשים:

git clone https://github.com/firebaseextended/codelab-dataconnect-web
cd codelab-dataconnect-web
cd ./app && npm i
npm run dev
  1. אחרי שמריצים את הפקודות האלה, פותחים את http://localhost:5173 בדפדפן כדי לראות את אפליקציית האינטרנט פועלת באופן מקומי. זהו החזית ליצירת אפליקציית ביקורות הסרטים ולאינטראקציה עם התכונות שלה.

93f6648a2532c606.png

שלב 2: פותחים את הפרויקט ב-Visual Studio Code

פותחים את התיקייה codelab-dataconnect-web שהועתק באמצעות Visual Studio Code. כאן תגדירו את הסכימה, תכתבו שאילתות ותבדקו את הפונקציונליות של האפליקציה.

שלב 3: התקנת התוסף של Firebase Data Connect ל-Visual Studio

כדי להשתמש בתכונות של Data Connect, צריך להתקין את התוסף של Firebase Data Connect ל-Visual Studio.לחלופין, אפשר להתקין אותו מ-Visual Studio Code Marketplace או לחפש אותו ב-VS Code.

  1. לחלופין, אפשר להתקין אותו מ-Visual Studio Code Marketplace או לחפש אותו ב-VS Code.

b03ee38c9a81b648.png

שלב 4: יוצרים פרויקט Firebase

אם עדיין אין לכם פרויקט Firebase, אתם יכולים להיכנס למסוף Firebase כדי ליצור פרויקט חדש. לאחר מכן, בתוסף Firebase Data Connect ל-VSCode:

  • לוחצים על הלחצן Sign in.
  • לוחצים על קישור פרויקט Firebase ובוחרים את הפרויקט שיצרתם במסוף Firebase.

4bb2fbf8f9fac29b.png

שלב 5: מפעילים את אמולטורים של Firebase

בתוסף Firebase Data Connect ל-VSCode, לוחצים על Start Emulators (הפעלת המהדמנים) ומוודאים שהמהדמנים פועלים במסוף.

6d3d95f4cb708db1.png

2. בדיקת קוד המקור של ערכת ההתחלה

בקטע הזה נסקור תחומים מרכזיים בקוד המקור של ערכת ההתחלה של האפליקציה. באפליקציה חסרות פונקציות מסוימות, אבל כדאי להבין את המבנה הכללי שלה.

מבנה התיקיות והקבצים

הנה סקירה כללית מהירה של מבנה התיקיות והקבצים באפליקציה:

dataconnect/

מכיל הגדרות של Firebase Data Connect, מחברים (שמגדירים שאילתות ומוטציות) וקובצי סכימה.

  • schema/schema.gql: הגדרת הסכימה של GraphQL
  • connector/queries.gql: שאילתות שנדרשות באפליקציה.
  • connector/mutations.gql: המוטציות הנדרשות באפליקציה.
  • connector/connector.yaml: קובץ תצורה ליצירת SDK

app/src/

מכיל את הלוגיקה של האפליקציה ואת האינטראקציה עם Firebase Data Connect.

  • firebase.ts: הגדרה להתחברות לאפליקציית Firebase במסוף.
  • lib/dataconnect-sdk/: התיקייה הזו מכילה את ה-SDK שנוצר. אפשר לערוך את המיקום של יצירת ה-SDK בקובץ connector/connector.yaml, ו-SDKs ייווצרו באופן אוטומטי בכל פעם שתגדירו שאילתה או מוטציה.

3. הגדרת הסכימה לביקורת על סרט

בקטע הזה מגדירים את המבנה ואת היחסים בין ישויות המפתח באפליקציית הסרטים בסכימה. ישויות כמו Movie,‏ User,‏ Actor ו-Review ממפות לטבלאות של מסדי נתונים, והיחסים נוצרים באמצעות Firebase Data Connect והוראות הסכימה של GraphQL. אחרי שתגדירו את הרכיב, האפליקציה תהיה מוכנה לטפל בכל דבר, החל מחיפוש סרטים עם הדירוג הגבוה ביותר וסינון לפי ז'אנר, ועד לאפשרות של המשתמשים לכתוב ביקורות, לסמן פריטים כ'מועדפים', לבחון סרטים דומים או למצוא סרטים מומלצים על סמך קלט טקסט באמצעות חיפוש וקטורים.

ישויות ויחסים מרכזיים

הסוג Movie מכיל פרטים חשובים כמו כותרת, ז'אנר ותגים, שהאפליקציה משתמשת בהם בחיפושים ובפרופילים של סרטים. הסוג User עוקב אחרי אינטראקציות של משתמשים, כמו ביקורות ומועדפים. Reviews לקשר משתמשים לסרטים, ולאפשר לאפליקציה להציג דירוגים ומשוב שנוצרו על ידי משתמשים.

הקשרים בין סרטים, שחקנים ומשתמשים הופכים את האפליקציה לדינמית יותר. טבלת המיזוג MovieActor עוזרת להציג את פרטי השחקנים ואת הפילמוגרפיות שלהם. הסוג FavoriteMovie מאפשר למשתמשים להוסיף סרטים למועדפים, כך שהאפליקציה יכולה להציג רשימת מועדפים מותאמת אישית ולהדגיש בחירות פופולריות.

טבלת סרטים

הסוג Movie מגדיר את המבנה הראשי של ישות סרט, כולל שדות כמו title,‏ genre,‏ releaseYear ו-rating.

מעתיקים ומדביקים את קטע הקוד בקובץ 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]
}

מסקנות עיקריות:

  • id: מזהה UUID ייחודי לכל סרט, שנוצר באמצעות @default(expr: "uuidV4()").

טבלת MovieMetadata

הסוג MovieMetadata יוצר יחסי אחד-לאחד עם הסוג Movie. הוא כולל נתונים נוספים, כמו הבמאי של הסרט.

מעתיקים ומדביקים את קטע הקוד בקובץ 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
}

מסקנות עיקריות:

  • סרט! @ref: הפניה לסוג Movie, שמגדירה קשר של מפתח זר.

טבלת שחקנים

מעתיקים ומדביקים את קטע הקוד בקובץ dataconnect/schema/schema.gql:

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

הסוג Actor מייצג שחקן במסד הנתונים של הסרטים, שבו כל שחקן יכול להשתתף בכמה סרטים, ויוצר יחס 'הרבה לרבים'.

טבלת MovieActor

מעתיקים ומדביקים את קטע הקוד בקובץ 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"
}

מסקנות עיקריות:

  • movie: הפניה לסוג Movie, יוצרת באופן משתמע מפתח זר movieId: UUID!.
  • actor: הפניה לסוג Actor, יוצרת באופן משתמע מפתח זר actorId: UUID!.
  • role: הגדרת התפקיד של השחקן בסרט (למשל, 'עיקרי' או 'תומך').

טבלת משתמשים

הסוג User מגדיר ישות משתמש שמקיימת אינטראקציה עם סרטים על ידי פרסום ביקורות או הוספת סרטים למועדפים.

מעתיקים ומדביקים את קטע הקוד בקובץ dataconnect/schema/schema.gql:

type User
  @table {
  id: String! @col(name: "auth_uid")
  username: String! @col(dataType: "varchar(50)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user 
  # movies_via_Review
}

טבלה FavoriteMovie

הטיפוס FavoriteMovie הוא טבלת צירוף שמטפלת ביחסי 'רבים לרבים' בין משתמשים לבין הסרטים או השחקנים המועדפים עליהם. כל טבלה מקשרת User ל-Movie.

מעתיקים ומדביקים את קטע הקוד בקובץ 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!
}

מסקנות עיקריות:

  • movie: הפניה לסוג Movie, יוצרת באופן משתמע מפתח זר movieId: UUID!.
  • user: הפניה לסוג המשתמש, יוצרת באופן משתמע מפתח זר userId: UUID!.

טבלת ביקורות

הסוג Review מייצג את הישות של הביקורת ומקשר בין הסוגים User ו-Movie ביחסות 'הרבה לרבים' (משתמש אחד יכול להשאיר הרבה ביקורות, וכל סרט יכול לכלול הרבה ביקורות).

מעתיקים ומדביקים את קטע הקוד בקובץ 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")
}

מסקנות עיקריות:

  • user: הפניה למשתמש שהשאיר את הביקורת.
  • movie: הפניה לסרט שעליו נכתבת הביקורת.
  • reviewDate: מוגדר באופן אוטומטי לזמן שבו הבדיקה נוצרה באמצעות @default(expr: "request.time").

שדות שמוגדרים כברירת מחדל ושדות שנוצרים באופן אוטומטי

בסכימה נעשה שימוש בביטויים כמו @default(expr: "uuidV4()") כדי ליצור באופן אוטומטי מזהים ייחודיים חותמות זמן. לדוגמה, השדה id בסוגי Movie ו-Review מאוכלס באופן אוטומטי ב-UUID כשיוצרים רשומה חדשה.

עכשיו, אחרי שהגדרתם את הסכימה, לאפליקציית הסרטים יש בסיס יציב למבנה הנתונים וליחסים שלה.

4. אחזור הסרטים המובילים והחדשים ביותר

אפליקציית FriendlyMovies

בקטע הזה תוסיפו נתוני סרטים מדומים למהדמרים המקומיים, ולאחר מכן תטמיעו את המחברים (שאילתות) ואת קוד TypeScript כדי לבצע קריאה למחברים האלה באפליקציית האינטרנט. בסיום, האפליקציה תוכל לאחזר באופן דינמי ולהציג את הסרטים החדשים והמובילים בדירוג ישירות ממסד הנתונים.

הוספת נתונים מדומים של סרטים, שחקנים וחוות דעת

  1. ב-VSCode, פותחים את dataconnect/moviedata_insert.gql. מוודאים שהמעבדים בסימולטורים של התוסף Firebase Data Connect פועלים.
  2. בחלק העליון של הקובץ אמור להופיע הלחצן הפעלה (מקומית). לוחצים עליו כדי להוסיף את נתוני הסרט המדומה למסד הנתונים.

e424f75e63bf2e10.png

  1. בודקים את מסוף Data Connect Execution כדי לוודא שהנתונים נוספו בהצלחה.

e0943d7704fb84ea.png

הטמעת המחבר

  1. פתיחת dataconnect/movie-connector/queries.gql. בתגובות תמצאו שאילתה בסיסית של ListMovies:
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

השאילתה הזו מאחזרת את כל הסרטים ואת הפרטים שלהם (למשל, מזהה, שם, שנת הפצה). עם זאת, היא לא ממיינת את הסרטים.

  1. מחליפים את השאילתה ListMovies בשאילתה הבאה כדי להוסיף אפשרויות מיון והגבלות:
# 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
  }
}

לוחצים על הלחצן Run (local) כדי להריץ את השאילתה במסד הנתונים המקומי. אפשר גם להזין את משתני השאילתה בחלונית ההגדרות לפני ההרצה.

c4d947115bb11b16.png

מסקנות עיקריות:

  • movies(): שדה שאילתת GraphQL לאחזור נתוני סרטים מהמסד נתונים.
  • orderByRating: פרמטר למיון סרטים לפי דירוג (עולה/יורד).
  • orderByReleaseYear: פרמטר למיון סרטים לפי שנת ההפצה (עולה/יורד).
  • limit: הגבלת מספר הסרטים שיוחזרו.

שילוב שאילתות באפליקציית האינטרנט

בקטע הזה תשתמשו בשאילתות שהוגדרו בקטע הקודם באפליקציית האינטרנט. המהדמנים של Firebase Data Connect יוצרים ערכות SDK על סמך המידע בקובצי ה-‎ .gql (schema.gql,‏ queries.gql,‏ mutations.gql) ובקובץ connector.yaml. אפשר להפעיל את ערכות ה-SDK האלה ישירות באפליקציה.

  1. בקובץ MovieService (app/src/lib/MovieService.tsx), מסירים את ההערה של הצהרת הייבוא בחלק העליון:
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

הפונקציה listMovies, סוג התגובה ListMoviesData וה-enum OrderDirection הן כולן ערכות SDK שנוצרות על ידי אמוללטורים של Firebase Data Connect על סמך הסכימה והשאילתות שהגדרתם קודם .

  1. מחליפים את הפונקציות handleGetTopMovies ו-handleGetLatestMovies בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • listMovies: פונקציה שנוצרת באופן אוטומטי שמפעילה את השאילתה listMovies כדי לאחזר רשימה של סרטים. הוא כולל אפשרויות למיון לפי דירוג או שנת פרסום ולהגבלת מספר התוצאות.
  • ListMoviesData: סוג התוצאה שמשמש להצגת 10 הסרטים המובילים והסרטים האחרונים בדף הבית.

הצגה בפעולה

צריך לטעון מחדש את אפליקציית האינטרנט כדי לראות את השאילתה בפעולה. עכשיו רשימת הסרטים מוצגת באופן דינמי בדף הבית, והנתונים מאוחזרים ישירות ממסד הנתונים המקומי. הסרטים עם הדירוג הגבוה ביותר והסרטים החדשים ביותר יופיעו בצורה חלקה, בהתאם לנתונים שהגדרתם.

5. הצגת פרטי הסרט והשחקן

בקטע הזה נטמיע את הפונקציונליות של אחזור מידע מפורט על סרט או שחקן באמצעות המזהים הייחודיים שלהם. התהליך הזה כולל לא רק אחזור נתונים מהטבלאות המתאימות, אלא גם צירוף של טבלאות קשורות כדי להציג פרטים מקיפים, כמו ביקורות על סרטים ופילוגרפיות של שחקנים.

ac7fefa7ff779231.png

הטמעת מחברים

  1. פותחים את dataconnect/movie-connector/queries.gql בפרויקט.
  2. מוסיפים את השאילתות הבאות כדי לאחזר פרטים של סרטים ושחקנים:
# Get movie by id
query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
 movie(id: $id) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    description
    tags
    metadata: movieMetadatas_on_movie {
      director
    }
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      name
      imageUrl
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      name
      imageUrl
    }
    reviews: reviews_on_movie {
      id
      reviewText
      reviewDate
      rating
      user {
        id
        username
      }
    }
  }
 }

# Get actor by id
query GetActorById($id: UUID!) @auth(level: PUBLIC) {
  actor(id: $id) {
    id
    name
    imageUrl
    mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      title
      genre
      tags
      imageUrl
    }
    supportingActors: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      title
      genre
      tags
      imageUrl
    }
  }
}
  1. שומרים את השינויים ובודקים את השאילתות.

מסקנות עיקריות:

  • movie()/actor(): שדות של שאילתות GraphQL לאחזור סרט או שחקן יחידים מהטבלה Movies או Actors.
  • _on_: האפשרות הזו מאפשרת גישה ישירה לשדות מסוג משויך שיש לו קשר למפתח זר. לדוגמה, reviews_on_movie מאחזרת את כל הביקורות שקשורות לסרט מסוים.
  • _via_: משמש לניווט ביחסים 'רבים לרבים' באמצעות טבלת איחוד. לדוגמה, actors_via_MovieActor ניגשת לסוג Actor דרך טבלת המיזוג MovieActor, והתנאי where מסנן שחקנים על סמך התפקיד שלהם (למשל, 'עיקרי' או 'תומך').

בחלונית ההפעלה של Data Connect, אפשר לבדוק את השאילתה על ידי הזנת מזהי מדגם, כמו:

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

לוחצים על הפעלה (מקומית) עבור GetMovieById כדי לאחזר את הפרטים על 'פרדוקס קוונטי' (סרט הפרודיה שהמזהה שלמעלה קשור אליו).

1b08961891e44da2.png

שילוב שאילתות באפליקציית האינטרנט

  1. בקובץ MovieService (app/src/lib/MovieService.tsx), מסירים את ההערות מהייבוא הבא:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. מחליפים את הפונקציות handleGetMovieById ו-handleGetActorById בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • getMovieById / getActorById: אלה פונקציות שנוצרות באופן אוטומטי ומפעילות את השאילתות שהגדרתם, כדי לאחזר מידע מפורט על סרט או שחקן ספציפיים.
  • GetMovieByIdData / GetActorByIdData: אלה סוגי התוצאות שמשמשים להצגת פרטי הסרטים והשחקנים באפליקציה.

הצגה בפעולה

עכשיו עוברים לדף הבית של אפליקציית האינטרנט. לוחצים על סרט כדי לראות את כל הפרטים שלו, כולל השחקנים והביקורות – מידע שנשלף מטבלאות קשורות. באופן דומה, לחיצה על שחקן תציג את הסרטים שבהם הוא השתתף.

6. טיפול באימות משתמשים

בקטע הזה נטמיע את הפונקציונליות של כניסת משתמשים לחשבון ויציאה ממנו באמצעות אימות ב-Firebase. תוכלו גם להשתמש בנתוני האימות מ-Firebase כדי לאחזר או להוסיף נתוני משתמשים ישירות ב-Firebase DataConnect, וכך להבטיח ניהול מאובטח של משתמשים באפליקציה.

9890838045d5a00e.png

הטמעת מחברים

  1. פותחים את mutations.gql ב-dataconnect/movie-connector/.
  2. מוסיפים את המוטציה הבאה כדי ליצור או לעדכן את המשתמש המאומת הנוכחי:
# Create or update the current authenticated user
mutation UpsertUser($username: String!) @auth(level: USER) {
  user_upsert(
    data: {
      id_expr: "auth.uid"
      username: $username
    }
  )
}

מסקנה עיקרית:

  • id_expr: "auth.uid": האפשרות הזו משתמשת ב-auth.uid, שמסופק ישירות על ידי אימות ב-Firebase ולא על ידי המשתמש או האפליקציה. כך מתווספת שכבת אבטחה נוספת, כי המערכת מוודאת שהטיפול במזהה המשתמש מתבצע באופן מאובטח ואוטומטי.

לאחר מכן, פותחים את queries.gql ב-dataconnect/movie-connector/.

מוסיפים את השאילתה הבאה כדי לאחזר את המשתמש הנוכחי:

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

מסקנות עיקריות:

  • auth.uid: הפרמטר הזה מאוחזר ישירות מאימות ב-Firebase, וכך מובטחת גישה מאובטחת לנתונים ספציפיים למשתמש.
  • _on_ שדות: השדות האלה מייצגים את טבלאות המיזוג:
  • reviews_on_user: אחזור כל הביקורות שקשורות למשתמש, כולל המזהה והשם של הסרט.
  • favorite_movies_on_user: אחזור של כל הסרטים שהמשתמש סימן כ'מועדפים', כולל מידע מפורט כמו ז'אנר, שנת הפקה, דירוג ומטא-נתונים.

שילוב שאילתות באפליקציית האינטרנט

  1. ב-MovieService‏ (app/src/lib/MovieService.tsx), מסירים את ההערות מהייבוא הבא:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. מחליפים את הפונקציות handleAuthStateChange ו-handleGetCurrentUser בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • handleAuthStateChange: הפונקציה הזו מקשיבה לשינויים במצב האימות. כשמשתמש נכנס לחשבון, המערכת מגדירה את הנתונים של המשתמש ומפעילה את המוטציה upsertUser כדי ליצור או לעדכן את פרטי המשתמש במסד הנתונים.
  • handleGetCurrentUser: אחזור הפרופיל של המשתמש הנוכחי באמצעות השאילתה getCurrentUser, שמאחזרת את הביקורות והסרטים האהובים על המשתמש.

הצגה בפעולה

עכשיו לוחצים על הלחצן 'כניסה באמצעות חשבון Google' בסרגל הניווט. אפשר להיכנס באמצעות אמולטור האימות של Firebase. אחרי הכניסה לחשבון, לוחצים על 'הפרופיל שלי'. בשלב הזה הוא יהיה ריק, אבל עכשיו כבר הגדרתם את הבסיס לטיפול בנתונים ספציפיים למשתמש באפליקציה.

7. הטמעה של אינטראקציות של משתמשים

בקטע הזה נטמיע אינטראקציות של משתמשים באפליקציה לביקורות על סרטים, כדי לאפשר למשתמשים לנהל את הסרטים האהובים עליהם ולכתוב או למחוק ביקורות.

b3d0ac1e181c9de9.png

הטמעת מחברים

  1. פותחים את mutations.gql ב-dataconnect/movie-connector/.
  2. מוסיפים את המוטציות הבאות כדי לטפל בהוספת סרטים למועדפים:
# 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 })
}

מסקנות עיקריות:

  • userId_expr: "auth.uid": משתמשים ב-auth.uid, שמסופק ישירות על ידי אימות ב-Firebase, כדי להבטיח שרק לנתונים של המשתמש המאומת תהיה גישה או שינוי.
  1. לאחר מכן, פותחים את queries.gql ב-dataconnect/movie-connector/.
  2. מוסיפים את השאילתה הבאה כדי לבדוק אם סרט מסוים נכלל ברשימת המועדפים:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}

מסקנות עיקריות:

  • auth.uid: מבטיחה גישה מאובטחת לנתונים ספציפיים למשתמש באמצעות אימות ב-Firebase.
  • favorite_movie: הפונקציה בודקת את טבלת המיזוג favorite_movies כדי לראות אם סרט ספציפי מסומן כמועדף על ידי המשתמש הנוכחי.

שילוב שאילתות באפליקציית האינטרנט

  1. בקובץ MovieService (app/src/lib/MovieService.tsx), מסירים את ההערות מהייבוא הבא:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. מחליפים את הפונקציות handleAddFavoritedMovie,‏ handleDeleteFavoritedMovie ו-handleGetIfFavoritedMovie בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • handleAddFavoritedMovie ו-handleDeleteFavoritedMovie: אפשר להשתמש במונטיזציות כדי להוסיף או להסיר סרט מהמועדפים של המשתמש באופן מאובטח.
  • handleGetIfFavoritedMovie: השאילתה getIfFavoritedMovie משמשת לבדיקה אם המשתמש סימן סרט כ'מועדף'.

הצגה בפעולה

עכשיו אפשר להוסיף סרטים למועדפים או לבטל את ההוספה שלהם על ידי לחיצה על סמל הלב בכרטיסי הסרטים ובדף הפרטים של הסרט. בנוסף, אתם יכולים לראות את הסרטים האהובים עליכם בדף הפרופיל.

הטמעת ביקורות של משתמשים

בשלב הבא, תטמיעו באפליקציה את הקטע לניהול ביקורות המשתמשים.

הטמעת מחברים

  1. בקובץ mutations.gql (dataconnect/movie-connector/mutations.gql): מוסיפים את המוטציות הבאות:
# 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 })
}

מסקנות עיקריות:

  • userId_expr: "auth.uid": מוודא שהביקורות משויכות למשתמש המאומת.
  • reviewDate_date: { today: true }: המערכת יוצרת באופן אוטומטי את התאריך הנוכחי של הבדיקה באמצעות DataConnect, כך שאין צורך להזין נתונים באופן ידני.

שילוב שאילתות באפליקציית האינטרנט

  1. בקובץ MovieService (app/src/lib/MovieService.tsx), מסירים את ההערות מהייבוא הבא:
import { addReview, deleteReview } from "@movie/dataconnect";
  1. מחליפים את הפונקציות handleAddReview ו-handleDeleteReview בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • handleAddReview: קריאה למוטציה addReview כדי להוסיף ביקורת על הסרט שצוין, ולקשר אותה בצורה מאובטחת למשתמש המאומת.
  • handleDeleteReview: משתמשים בטרנספורמציה deleteReview כדי להסיר ביקורת על סרט שהמשתמש המאומת פרסם.

הצגה בפעולה

עכשיו המשתמשים יכולים להשאיר ביקורות על סרטים בדף הפרטים של הסרט. הם יכולים גם לראות ולמחוק את הביקורות שלהם בדף הפרופיל שלהם, כך שיש להם שליטה מלאה על האינטראקציות שלהם עם האפליקציה.

8. מסננים מתקדמים והתאמה חלקית של טקסט

בקטע הזה נסביר איך מטמיעים יכולות חיפוש מתקדמות שמאפשרות למשתמשים לחפש סרטים לפי מגוון דירוגים ושנות הפקה, לסנן לפי ז'אנרים ותגים, לבצע התאמה חלקית של טקסט בשמות או בתיאורים ואפילו לשלב כמה מסננים כדי לקבל תוצאות מדויקות יותר.

ece70ee0ab964e28.png

הטמעת מחברים

  1. פתיחת queries.gql ב-dataconnect/movie-connector/.
  2. מוסיפים את השאילתה הבאה כדי לתמוך ביכולות חיפוש שונות:
# 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
    }
  }
}

מסקנות עיקריות:

  • אופרטור _and: שילוב של כמה תנאים בשאילתה אחת, שמאפשר לסנן את החיפוש לפי כמה שדות כמו releaseYear, ‏rating ו-genre.
  • אופרטור contains: חיפוש התאמות חלקיות של טקסט בתוך שדות. בשאילתה הזו, היא מחפשת התאמות ב-title, ב-description, ב-name או ב-reviewText.
  • תנאי where: מציין את התנאים לסינון הנתונים. בכל קטע (סרטים, שחקנים, ביקורות) נעשה שימוש בפסקה where כדי להגדיר את הקריטריונים הספציפיים לחיפוש.

שילוב שאילתות באפליקציית האינטרנט

  1. בקובץ MovieService (app/src/lib/MovieService.tsx), מסירים את ההערות מהייבוא הבא:
import { searchAll, SearchAllData } from "@movie/dataconnect";
  1. מחליפים את הפונקציה handleSearchAll בקוד הבא:
// 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;
  }
};

מסקנות עיקריות:

  • handleSearchAll: הפונקציה הזו משתמשת בשאילתה searchAll כדי לבצע חיפוש על סמך הקלט של המשתמש, ומסננת את התוצאות לפי פרמטרים כמו שנה, דירוג, ז'אנר והתאמות חלקיות של טקסט.

הצגה בפעולה

עוברים לדף 'חיפוש מתקדם' בסרגל הניווט באפליקציית האינטרנט. עכשיו אפשר לחפש סרטים, שחקנים וביקורות באמצעות מסננים ומקורות קלט שונים, ולקבל תוצאות חיפוש מפורטות ומותאמות אישית.

9. אופציונלי: פריסה ב-Cloud (נדרש חיוב)

עכשיו, אחרי שסיימתם את מחזור הפיתוח המקומי, הגיע הזמן לפרוס את הסכימה, הנתונים והשאילתות בשרת. אפשר לעשות זאת באמצעות התוסף Firebase Data Connect ל-VS Code או באמצעות ה-CLI של Firebase.

הוספת אפליקציית אינטרנט במסוף Firebase

  1. יוצרים אפליקציית אינטרנט במסוף Firebase ומתעדים את מזהה האפליקציה.

7030822793e4d75b.png

  1. מגדירים אפליקציית אינטרנט במסוף Firebase בלחיצה על 'הוספת אפליקציה'. בשלב הזה אפשר להתעלם מהגדרת ה-SDK ומהגדרת ההגדרות, אבל חשוב לשים לב לאובייקט firebaseConfig שנוצר.
  2. מחליפים את firebaseConfig ב-app/src/lib/firebase.tsx:
const firebaseConfig = {
  apiKey: "API_KEY",
  authDomain: "PROJECT_ID.firebaseapp.com",
  projectId: "PROJECT_ID",
  storageBucket: "PROJECT_ID.appspot.com",
  messagingSenderId: "SENDER_ID",
  appId: "APP_ID"
};
  1. יצירת אפליקציית האינטרנט: בתיקייה app, משתמשים ב-Vite כדי ליצור את אפליקציית האינטרנט לפריסה באירוח:
cd app
npm run build

הגדרת אימות ב-Firebase במסוף

  1. הגדרת Firebase Auth עם 'כניסה באמצעות חשבון Google'

62af2f225e790ef6.png

  1. (אופציונלי) מאפשרים דומיינים ל-(Firebase Auth)‏ [https://firebase.google.com/docs/auth/web/hosting] במסוף הפרויקט (למשל, http://127.0.0.1):
  • בהגדרות האימות, בוחרים את הפרויקט ועוברים אל (Authorized Domains) (דומיינים מורשים) [https://firebase.google.com/docs/auth/web/hosting]. לוחצים על 'הוספת דומיין' וכוללים את הדומיין המקומי ברשימה.

c255098f12549886.png

פריסה באמצעות Firebase CLI

  1. בקובץ dataconnect/dataconnect.yaml, מוודאים שמזהה המכונה, מסד הנתונים ומזהה השירות תואמים לפרויקט:
specVersion: "v1alpha"
serviceId: "your-service-id"
location: "us-central1"
schema:
  source: "./schema"
  datasource:
    postgresql:
      database: "your-database-id"
      cloudSql:
        instanceId: "your-instance-id"
connectorDirs: ["./movie-connector"]
  1. מוודאים שהגדרתם את Firebase CLI בפרויקט
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. מריצים את הפקודה הבאה בטרמינל כדי לפרוס:
firebase deploy --only dataconnect,hosting
  1. מריצים את הפקודה הבאה כדי להשוות את השינויים בסכימה:
firebase dataconnect:sql:diff
  1. אם השינויים מקובלים, מחילים אותם באמצעות:
firebase dataconnect:sql:migrate

המכונה של Cloud SQL for PostgreSQL תתעדכן בנתונים ובסכימה הסופיים שנפרסו. אפשר לעקוב אחרי הסטטוס במסוף Firebase.

עכשיו אמורה להופיע האפליקציה שלך ב-your-project.web.app/. בנוסף, אפשר ללחוץ על הפעלה (ייצור) בחלונית של Firebase Data Connect, בדיוק כמו שעשיתם עם המהדמנים המקומיים, כדי להוסיף נתונים לסביבת הייצור.

10. אופציונלי: חיפוש וקטורים באמצעות Firebase Data Connect

בקטע הזה מפעילים חיפוש וקטורים באפליקציה של ביקורות על סרטים באמצעות Firebase Data Connect. התכונה הזו מאפשרת לבצע חיפושים מבוססי-תוכן, כמו חיפוש סרטים עם תיאורים דומים באמצעות הטמעת וקטורים.

4b5aca5a447d2feb.png

עדכון הסכימה כך שתכלול הטמעות של שדה

  1. ב-dataconnect/schema/schema.gql, מוסיפים את השדה descriptionEmbedding לטבלה 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
}

מסקנה עיקרית:

  • descriptionEmbedding: Vector @col(size:768): השדה הזה מאחסן את הטמעות הסמנטיקה של תיאורי הסרטים, ומאפשר חיפוש תוכן מבוסס-וקטור באפליקציה.

הפעלת Vertex AI

  1. פועלים לפי המדריך לדרישות מוקדמות כדי להגדיר את ממשקי Vertex AI API ב-Google Cloud. השלב הזה חיוני כדי לתמוך ביצירת הטמעה ובפונקציונליות של חיפוש וקטורים.
  2. כדי להפעיל את pgvector ואת החיפוש לפי וקטור, פורסים מחדש את הסכימה בלחיצה על 'פריסה בסביבת הייצור' באמצעות התוסף של Firebase Data Connect ל-VSCode.

איך מאכלסים את מסד הנתונים באמצעות הטמעות (embeddings)

  1. פותחים את התיקייה dataconnect ב-VSCode ולוחצים על Run(local) ב-optional_vector_embed.gql כדי לאכלס את מסד הנתונים ב-embeddings של הסרטים.

b858da780f6ec103.png

הוספת שאילתה לחיפוש וקטור

  1. כדי לבצע חיפושים של וקטורים, מוסיפים את השאילתה הבאה לקובץ dataconnect/movie-connector/queries.gql:
# 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
  }
}

מסקנות עיקריות:

  • compare_embed: מציין את מודל הטמעת הטקסט (textembedding-gecko@003) ואת טקסט הקלט ($query) לצורך השוואה.
  • method: מציין את שיטת הדמיון (L2), שמייצגת את המרחק האוקלידי.
  • within: הגבלת החיפוש לסרטים עם מרחק L2 של 2 או פחות, תוך התמקדות בהתאמות תוכן דומות.
  • limit: הגבלת מספר התוצאות שהוחזרו ל-5.

הטמעת פונקציית החיפוש לפי וקטור באפליקציה

  1. בקובץ app/src/lib/MovieService.ts, מבטלים את ההערות של קובצי הייבוא הבאים:

הטמעת פונקציית החיפוש לפי וקטור באפליקציה

עכשיו, אחרי שהסכימת והגדרת את השאילתה, אפשר לשלב את חיפוש הווקטורים בשכבת השירות של האפליקציה. השלב הזה מאפשר לכם להפעיל את שאילתת החיפוש מאפליקציית האינטרנט.

בקובץ app/src/lib/ MovieService.ts, מסירים את ההערה מהייבוא הבא של ערכות ה-SDK. זה יפעל כמו כל שאילתה אחרת.

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

מוסיפים את הפונקציה הבאה כדי לשלב חיפוש מבוסס-וקטורים באפליקציה:

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


מסקנות עיקריות:

  • searchMoviesByDescription: הפונקציה הזו קוראת לשאילתה searchMovieDescriptionUsingL2similarity, מעבירה את טקסט הקלט כדי לבצע חיפוש תוכן מבוסס-וקטור.

הצגה בפעולה

עוברים לקטע 'חיפוש וקטור' בסרגל הניווט ומקלידים ביטויים כמו 'רומנטי ומודרני'. תוצג רשימה של סרטים שתואמים לתוכן שאתם מחפשים. לחלופין, תוכלו להיכנס לדף הפרטים של סרט כלשהו ולעיין בקטע'סרטים דומים' שבתחתית הדף.

7b71f1c75633c1be.png

11. סיכום

מעולה, עכשיו אמורה להיות לך אפשרות להשתמש באפליקציית האינטרנט. אם אתם רוצים לשחק עם נתוני סרטים משלכם, אל דאגה, תוכלו להוסיף נתונים משלכם באמצעות התוסף FDC על ידי חיקוי של קובצי _insert.gql, או להוסיף אותם באמצעות חלונית הביצוע של Data Connect.

מידע נוסף