שילוב של Firebase עם אפליקציית Next.js

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

בסדנת התכנות הזו תלמדו איך לשלב את Firebase עם אפליקציית אינטרנט של Next.js שנקראת Friendly Eats, שהיא אתר לביקורות על מסעדות.

אפליקציית האינטרנט Friendly Eats

אפליקציית האינטרנט המוגמרת מציעה תכונות שימושיות שמדגימות איך Firebase יכול לעזור לכם לבנות אפליקציות Next.js. התכונות האלה כוללות:

  • יצירה ופריסה אוטומטיות: ב-codelab הזה נעשה שימוש ב-Firebase App Hosting כדי ליצור ולפרוס באופן אוטומטי את קוד Next.js בכל פעם שמעלים אותו לענף מוגדר.
  • כניסה ויציאה: אפליקציית האינטרנט המלאה מאפשרת לכם להיכנס באמצעות חשבון Google ולצאת ממנו. הכניסה של המשתמשים וההתמדה שלהם מנוהלות באופן מלא דרך אימות ב-Firebase.
  • תמונות: אפליקציית האינטרנט שהושלמה מאפשרת למשתמשים מחוברים להעלות תמונות של מסעדות. נכסי התמונות מאוחסנים ב-Cloud Storage for Firebase. ‫Firebase JavaScript SDK מספק כתובת URL ציבורית לתמונות שהועלו. כתובת ה-URL הציבורית הזו מאוחסנת במסמך הרלוונטי של המסעדה ב-Cloud Firestore.
  • ביקורות: אפליקציית האינטרנט המלאה מאפשרת למשתמשים מחוברים לפרסם ביקורות על מסעדות, שכוללות דירוג כוכבים והודעה מבוססת-טקסט. המידע על הביקורות מאוחסן ב-Cloud Firestore.
  • מסננים: אפליקציית האינטרנט המלאה מאפשרת למשתמשים מחוברים לסנן את רשימת המסעדות לפי קטגוריה, מיקום ומחיר. אפשר גם להתאים אישית את שיטת המיון שבה משתמשים. הגישה לנתונים מתבצעת מ-Cloud Firestore, והשאילתות ב-Firestore מוחלות על סמך המסננים שבהם נעשה שימוש.

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

  • חשבון GitHub
  • ידע ב-Next.js וב-JavaScript

מה תלמדו

מה צריך להכין

  • Git
  • גרסה יציבה עדכנית של Node.js
  • דפדפן לבחירתכם, כמו Google Chrome
  • סביבת פיתוח עם עורך קוד וטרמינל
  • חשבון Google ליצירה ולניהול של פרויקט Firebase
  • האפשרות לשדרג את פרויקט Firebase לתוכנית התמחור Blaze

2. הגדרת סביבת הפיתוח ומאגר ב-GitHub

ב-Codelab הזה מסופק בסיס הקוד של האפליקציה, והוא מסתמך על Firebase CLI.

יצירת מאגר ב-GitHub

קוד המקור של ה-Codelab זמין בכתובת https://github.com/firebase/friendlyeats-web. המאגר מכיל פרויקטים לדוגמה למספר פלטפורמות. עם זאת, ב-codelab הזה נעשה שימוש רק בספרייה nextjs-start. חשוב לשים לב לספריות הבאות:

* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.

מעתיקים את התיקייה nextjs-start למאגר משלכם:

  1. באמצעות מסוף, יוצרים תיקייה חדשה במחשב ועוברים לספרייה החדשה:
    mkdir codelab-friendlyeats-web
    
    cd codelab-friendlyeats-web
    
  2. משתמשים בחבילת ה-npm‏ giget כדי לאחזר רק את התיקייה nextjs-start:
    npx giget@latest "gh:firebase/friendlyeats-web/nextjs-start#master" . --install
    
  3. מעקב אחרי שינויים באופן מקומי באמצעות Git:
    git init
    
    git add .
    
    git commit -m "Codelab starting point"
    
    git branch -M main
    
  4. יוצרים מאגר חדש ב-GitHub: https://github.com/new. נותנים לו שם שרוצים.
  5. בהתאם לשיטת האימות ב-GitHub (HTTPS או SSH), מעתיקים את כתובת ה-URL החדשה שנוצרה ב-GitHub:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git או
    • git@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
  6. מריצים את הפקודה הבאה כדי לדחוף את השינויים המקומיים למאגר החדש ב-GitHub. מחליפים את הפלייס הולדר <REPOSITORY_URL> בכתובת ה-URL של המאגר בפועל.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. עכשיו אמור להופיע קוד ההתחלה במאגר GitHub.

התקנה או עדכון של Firebase CLI

מריצים את הפקודה הבאה כדי לוודא שה-CLI של Firebase מותקן ושהגרסה שלו היא 14.1.0 ואילך:

firebase --version

אם מופיעה גרסה ישנה יותר או אם Firebase CLI לא מותקן, מריצים את פקודת ההתקנה:

npm install -g firebase-tools@latest

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

התחברות ל-Firebase

  1. מריצים את הפקודה הבאה כדי להתחבר ל-Firebase CLI:
    firebase login
    
  2. מזינים Y או N, בהתאם לרצון שלכם ש-Firebase יאסוף נתונים.
  3. בדפדפן, בוחרים את חשבון Google ולוחצים על אישור.

3. הגדרת פרויקט Firebase

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

יצירת פרויקט Firebase

  1. נכנסים למסוף Firebase באמצעות אותו חשבון Google שבו השתמשתם בשלב הקודם.
  2. לוחצים על הלחצן כדי ליצור פרויקט חדש, ואז מזינים שם לפרויקט (לדוגמה, FriendlyEats Codelab).
  3. לוחצים על המשך.
  4. אם מוצגת בקשה לעשות זאת, קוראים ומאשרים את התנאים של Firebase, ואז לוחצים על המשך.
  5. (אופציונלי) מפעילים את העזרה מבוססת-AI במסוף Firebase (שנקראת Gemini ב-Firebase).
  6. ב-codelab הזה לא צריך להשתמש ב-Google Analytics, ולכן משביתים את האפשרות Google Analytics.
  7. לוחצים על יצירת פרויקט, מחכים שהפרויקט יוקצה ולוחצים על המשך.

שדרוג תוכנית התמחור של Firebase

כדי להשתמש ב-Firebase App Hosting וב-Cloud Storage for Firebase, הפרויקט ב-Firebase צריך להיות בתוכנית התמחור 'תשלום לפי שימוש' (Blaze), כלומר הוא צריך להיות מקושר לחשבון לחיוב ב-Cloud.

כדי לשדרג את הפרויקט לתוכנית Blaze, פועלים לפי השלבים הבאים:

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

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

  1. עוברים אל סקירת הפרויקט בפרויקט Firebase, לוחצים על הוספת אפליקציה ואז על אינטרנט.
  2. בתיבת הטקסט כינוי לאפליקציה, מזינים כינוי קליט לאפליקציה, כמו My Next.js app.
  3. לא מסמנים את התיבה Also set up Firebase Hosting for this app (הגדרת אירוח ב-Firebase גם לאפליקציה הזו).
  4. לוחצים על Register app > Continue to console (רישום האפליקציה > המשך אל המסוף).

הגדרה של שירותי Firebase במסוף Firebase

הגדרת אימות

  1. בחלונית הימנית במסוף Firebase, מרחיבים את Build (פיתוח) ובוחרים באפשרות Authentication (אימות).
  2. לוחצים על תחילת העבודה.
  3. בעמודה ספקי כניסה, לוחצים על Google > הפעלה.
  4. בתיבת הטקסט Public-facing name for project, מזינים שם קליט, כמו My Next.js app.
  5. בתפריט הנפתח Support email for project, בוחרים את כתובת האימייל.
  6. לוחצים על שמירה.

הגדרה של Cloud Firestore

  1. בחלונית הימנית של מסוף Firebase, מרחיבים את Build ובוחרים באפשרות Firestore Database.
  2. לוחצים על יצירת מסד נתונים.
  3. בוחרים באפשרות מהדורה רגילה ולוחצים על הבא.
  4. לא משנים את מזהה מסד הנתונים, משאירים אותו כפי שהוא (default).
  5. בוחרים מיקום למסד הנתונים ולוחצים על הבא.
    באפליקציה אמיתית, כדאי לבחור מיקום שקרוב למשתמשים.
  6. לוחצים על התחלה במצב בדיקה. קוראים את כתב הוויתור בנוגע לכללי האבטחה.
    בהמשך ה-codelab הזה, תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אל תפיצו או תחשפו אפליקציה באופן ציבורי בלי להוסיף כללי אבטחה למסד הנתונים.
  7. לוחצים על יצירה.

הגדרת Cloud Storage for Firebase

  1. בחלונית הימנית במסוף Firebase, מרחיבים את Build (פיתוח) ובוחרים באפשרות Storage (אחסון).
  2. לוחצים על תחילת העבודה.
  3. בוחרים מיקום לקטגוריית האחסון שמוגדרת כברירת מחדל.
    קטגוריות ב-US-WEST1, ב-US-CENTRAL1 וב-US-EAST1 יכולות ליהנות מהמסלול תמיד בחינם של Google Cloud Storage. התמחור והשימוש בקטגוריות בכל המיקומים האחרים מפורטים בתמחור ובשימוש ב-Google Cloud Storage.
  4. לוחצים על התחלה במצב בדיקה. קוראים את כתב הוויתור בנוגע לכללי האבטחה.
    בהמשך ה-codelab, תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אל תפיצו או תחשפו אפליקציה לציבור בלי להוסיף כללי אבטחה לדלי שלכם ב-Storage.
  5. לוחצים על יצירה.

פריסת כללי אבטחה

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

  1. בטרמינל, מגדירים את ה-CLI לשימוש בפרויקט Firebase שיצרתם קודם:
    firebase use --add
    
    כשמופיעה בקשה להזין כינוי, מזינים friendlyeats-codelab.
  2. כדי לפרוס את כללי האבטחה האלה (וגם אינדקסים שיידרשו בהמשך), מריצים את הפקודה הבאה בטרמינל:
    firebase deploy --only firestore,storage
    
  3. אם מוצגת השאלה: "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?", מקישים על Enter כדי לבחור באפשרות כן.

4. בדיקת בסיס הקוד הראשוני

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

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

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

תיקיות וקבצים

תיאור

src/components

רכיבי React למסננים, לכותרות, לפרטי מסעדות ולביקורות

src/lib

פונקציות עזר שלא בהכרח קשורות ל-React או ל-Next.js

src/lib/firebase

קוד ספציפי ל-Firebase והגדרת Firebase

public

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

src/app

ניתוב באמצעות נתב האפליקציות של Next.js

package.json וגם package-lock.json

יחסי תלות של פרויקטים ב-npm

next.config.js

הגדרה ספציפית ל-Next.js (פעולות שרת מופעלות)

jsconfig.json

הגדרות של שירות שפה ב-JavaScript

רכיבי שרת ולקוח

האפליקציה היא אפליקציית אינטרנט ב-Next.js שמשתמשת בנתב האפליקציות. הרינדור בצד השרת משמש בכל האפליקציה. לדוגמה, קובץ src/app/page.js הוא רכיב בצד השרת שאחראי על הדף הראשי. הקובץ src/components/RestaurantListings.jsx הוא רכיב לקוח שמסומן על ידי ההנחיה "use client" בתחילת הקובץ.

ייבוא דפי פירוט חשבון

יכול להיות שתראו הצהרות ייבוא כמו אלה:

import RatingPicker from "@/src/components/RatingPicker.jsx";

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

ממשקי API ספציפיים ל-Firebase

כל קוד Firebase API עטוף בספרייה src/lib/firebase. לאחר מכן, כל רכיב React מייבא את הפונקציות העטופות מהספרייה src/lib/firebase, במקום לייבא ישירות פונקציות של Firebase.

נתוני דמה

נתוני מסעדות וביקורות לדוגמה מופיעים בקובץ src/lib/randomData.js. הנתונים מהקובץ הזה מורכבים בקוד בקובץ src/lib/fakeRestaurants.js.

5. יצירת קצה עורפי לאירוח אפליקציות

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

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

יצירת קצה עורפי

  1. במסוף Firebase, עוברים אל App Hosting.
  2. לוחצים על Get started (התחלה) כדי להתחיל את תהליך היצירה של ה-Backend.
  3. בוחרים אזור. באפליקציה אמיתית, צריך לבחור את האזור שהכי קרוב למשתמשים.
  4. פועלים לפי ההנחיות בשלב ייבוא מאגר מ-GitHub כדי להגדיר אימות ב-GitHub.
  5. בקטע Repository, בוחרים באפשרות Grant access to a new repository in GitHub (מתן גישה למאגר חדש ב-GitHub) ופועלים לפי ההנחיות כדי להעניק גישה למאגר ב-GitHub שיצרתם קודם.
  6. לוחצים על רענון הרשימה כדי לרענן את הרשימה, בוחרים את המאגר ולוחצים על הבא.
  7. הגדרת הגדרות הפריסה:
    1. מגדירים את הענף הפעיל לערך main.
    2. משאירים את ספריית השורש כ-/.
    3. הפעלת השקות אוטומטיות.
  8. נותנים שם לחלק האחורי של האתר friendlyeats-codelab ולוחצים על הבא.
  9. בקטע Associate a Firebase web app (שיוך אפליקציית אינטרנט ל-Firebase), בוחרים באפשרות Select an existing Firebase web app (בחירת אפליקציית אינטרנט קיימת ל-Firebase) ובוחרים את האפליקציה שהוספתם מהרשימה.
  10. לוחצים על סיום ופריסה. תועברו לדף חדש שבו תוכלו לראות את הסטטוס של העורף החדש של אירוח האפליקציה.
  11. לוחצים על View (הצגה) כדי לראות מידע נוסף על פריסת אירוח האפליקציה, כולל סטטוס ההשקה, יומנים ופרטי שימוש.
  12. אחרי שההשקה מסתיימת, לוחצים כדי לפתוח את כתובת האתר בקטע דומיינים. יכול להיות שיחלפו כמה דקות עד שהשינוי יתעדכן ב-DNS.
  13. אוי ואבוי! כשמטעינים את הדף, מופיעה הודעת שגיאה: "שגיאת אפליקציה: אירעה חריגה בצד השרת (למידע נוסף, אפשר לעיין ביומני השרת)".
  14. במסוף Firebase, בודקים את הכרטיסייה Logs (יומנים) של העורף של App Hosting. יוצג יומן עם השגיאה "לא בוצעה הטמעה". בשלב הבא נוסיף אימות כדי לתקן את הבעיה.

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

6. הוספת אימות לאפליקציית האינטרנט

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

הוספת דומיין מורשה

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

  1. פותחים את הדף App Hosting (אירוח אפליקציות) ולוחצים על View (הצגה) מתחת לאתר שהופעל כדי לגשת לדף Overview (סקירה כללית). מעתיקים את שם הדומיין של העורף (backend) של אירוח האפליקציות.
  2. עוברים אל הכרטיסייה 'הגדרות הרשאה' ובוחרים את הפרויקט שרוצים להוסיף לו דומיין מורשה. לאחר מכן מאתרים את הקטע דומיינים מורשים ולוחצים עליו.
  3. לוחצים על הלחצן הוספת דומיין.
  4. מזינים את הדומיין של ה-backend של App Hosting.
  5. לוחצים על הוספה.

הטמעה של פונקציות הכניסה והיציאה

בקובץ src/lib/firebase/auth.js, מחליפים את הפונקציות onAuthStateChanged, onIdTokenChanged, signInWithGoogle ו-signOut בקוד הבא:

export function onAuthStateChanged(cb) {
  return _onAuthStateChanged(auth, cb);
}

export function onIdTokenChanged(cb) {
  return _onIdTokenChanged(auth, cb);
}

export async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();

  try {
    await signInWithPopup(auth, provider);
  } catch (error) {
    console.error("Error signing in with Google", error);
  }
}

export async function signOut() {
  try {
    return auth.signOut();
  } catch (error) {
    console.error("Error signing out with Google", error);
  }
}

הקוד הזה משתמש בממשקי ה-API הבאים של Firebase:

Firebase API

תיאור

auth.onAuthStateChanged

הפונקציה מוסיפה צופה לשינויים בסטטוס הכניסה של המשתמש.

auth.onIdTokenChanged

הוספת משקיף לשינויים בטוקן ה-ID של המשתמש.

GoogleAuthProvider

יצירת מופע של ספק אימות של Google.

signInWithPopup

מתחיל תהליך אימות מבוסס-דיאלוג.

auth.signOut

יציאה מהחשבון של המשתמש.

בקובץ src/components/Header.jsx, הקוד כבר מפעיל את הפונקציות signInWithGoogle ו-signOut.

שליחת מצב האימות לשרת

כדי להעביר את מצב האימות לשרת, נשתמש בקובצי Cookie. בכל פעם שמצב האימות משתנה בלקוח, אנחנו מעדכנים את קובץ ה-Cookie ‏__session.

ב-src/components/Header.jsx, מחליפים את הפונקציה useUserSession בקוד הבא:

function useUserSession(initialUser) {
  useEffect(() => {
    return onIdTokenChanged(async (user) => {
      if (user) {
        const idToken = await user.getIdToken();
        await setCookie("__session", idToken);
      } else {
        await deleteCookie("__session");
      }
      if (initialUser?.uid === user?.uid) {
        return;
      }
      window.location.reload();
    });
  }, [initialUser]);

  return initialUser;
}

קריאת מצב האימות בשרת

נשתמש ב-FirebaseServerApp כדי לשקף את מצב האימות של הלקוח בשרת.

פותחים את src/lib/firebase/serverApp.js ומחליפים את הפונקציה getAuthenticatedAppForUser:

export async function getAuthenticatedAppForUser() {
  const authIdToken = (await cookies()).get("__session")?.value;

  // Firebase Server App is a new feature in the JS SDK that allows you to
  // instantiate the SDK with credentials retrieved from the client & has
  // other affordances for use in server environments.
  const firebaseServerApp = initializeServerApp(
    // https://github.com/firebase/firebase-js-sdk/issues/8863#issuecomment-2751401913
    initializeApp(),
    {
      authIdToken,
    }
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

אימות השינויים

פריסת הבסיס בקובץ src/app/layout.js מעבדת את הכותרת ומעבירה את המשתמש כמאפיין, אם הוא זמין.

<Header initialUser={currentUser?.toJSON()} />

כלומר, רכיב <Header> מעבד נתוני משתמש, אם הם זמינים, במהלך זמן הריצה של השרת. אם יש עדכוני אימות במהלך מחזור החיים של הדף אחרי הטעינה הראשונית של הדף, הם מטופלים על ידי ה-handler‏ onAuthStateChanged.

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

  1. יוצרים קומיט עם הודעת הקומיט 'הוספת אימות' ומבצעים push למאגר ב-GitHub.
    git add .
    
    git commit -m "Add authentication"
    
    git push
    
  2. פותחים את הדף App Hosting, וכשההשקה החדשה מסתיימת, לוחצים על כתובת ה-URL של האתר כדי לפתוח אותו.
  3. בדיקת האימות:
    1. נכנסים לחשבון Google ומוודאים ששם התצוגה מופיע בכותרת אחרי הכניסה.
    2. יוצאים מהחשבון ונכנסים אליו שוב. אפשר לחזור על השלב הזה עם משתמשים אחרים.
    3. אופציונלי: לוחצים לחיצה ימנית על אפליקציית האינטרנט, בוחרים באפשרות הצגת מקור הדף ומחפשים את השם המוצג. הוא מופיע במקור ה-HTML הגולמי שמוחזר מהשרת.

7. הצגת פרטי המסעדה

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

הוספה של מסעדה אחת או יותר

כדי להוסיף נתוני מסעדה לדוגמה למסד הנתונים המקומי של Cloud Firestore, פועלים לפי השלבים הבאים:

  1. אם עדיין לא עשיתם זאת, נכנסים לאפליקציית האינטרנט. לאחר מכן בוחרים באפשרות 2cf67d488d8e6332.png> הוספת מסעדות לדוגמה. שימו לב שלא מופיעות מסעדות באפליקציית האינטרנט Friendly Eats כי עדיין לא הגדרנו קוד לאחזור נתונים. נפתור את הבעיה הזו בשלב הבא.
  2. במסוף Firebase, בדף Firestore Database, בוחרים באפשרות restaurants. מוצגים לכם מסמכים ברמה העליונה באוסף המסעדות, שכל אחד מהם מייצג מסעדה.
  3. כדאי ללחוץ על כמה מסמכים כדי לראות את המאפיינים של מסמך מסעדה.

הצגת רשימת המסעדות

במסד הנתונים של Cloud Firestore יש עכשיו מסעדות שאפליקציית האינטרנט של Next.js יכולה להציג.

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

  1. בקובץ src/app/page.js, מוצאים את רכיב השרת <Home /> ובודקים את הקריאה לפונקציה getRestaurants, שמחזירה רשימה של מסעדות בזמן הריצה של השרת. בשלבים הבאים נטמיע את הפונקציה getRestaurants.
  2. בקובץ src/lib/firebase/firestore.js, מחליפים את הפונקציות applyQueryFilters ו-getRestaurants בקוד הבא:
function applyQueryFilters(q, { category, city, price, sort }) {
  if (category) {
    q = query(q, where("category", "==", category));
  }
  if (city) {
    q = query(q, where("city", "==", city));
  }
  if (price) {
    q = query(q, where("price", "==", price.length));
  }
  if (sort === "Rating" || !sort) {
    q = query(q, orderBy("avgRating", "desc"));
  } else if (sort === "Review") {
    q = query(q, orderBy("numRatings", "desc"));
  }
  return q;
}

export async function getRestaurants(db = db, filters = {}) {
  let q = query(collection(db, "restaurants"));

  q = applyQueryFilters(q, filters);
  const results = await getDocs(q);
  return results.docs.map((doc) => {
    return {
      id: doc.id,
      ...doc.data(),
      // Only plain objects can be passed to Client Components from Server Components
      timestamp: doc.data().timestamp.toDate(),
    };
  });
}
  1. יוצרים קומיט עם הודעת הקומיט Read the list of restaurants from Firestore (קריאת רשימת המסעדות מ-Firestore) ומבצעים push שלו למאגר ב-GitHub.
    git add .
    
    git commit -m "Read the list of restaurants from Firestore"
    
    git push
    
  2. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  3. באפליקציית האינטרנט, מרעננים את הדף. תמונות של מסעדות מופיעות כמשבצות בדף.

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

כשמשתמשים ב-framework‏ Next.js, יכול להיות שלא ברור מתי הנתונים נטענים בזמן הריצה של השרת או בזמן הריצה בצד הלקוח.

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

  1. באפליקציית האינטרנט, פותחים את כלי הפיתוח ומשביתים את JavaScript.

השבתת JavaScript בכלי הפיתוח

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

האזנה לעדכונים לגבי מסעדות באמצעות מאזיני תמונת מצב של Cloud Firestore

בקטע הקודם ראיתם איך נטען סט המסעדות הראשוני מקובץ src/app/page.js. הקובץ src/app/page.js הוא רכיב בצד השרת והוא מעובד בשרת, כולל קוד אחזור הנתונים של Firebase.

הקובץ src/components/RestaurantListings.jsx הוא רכיב לקוח שאפשר להגדיר אותו כדי להוסיף נתונים לסימון שמוצג בשרת.

כדי להגדיר את הקובץ src/components/RestaurantListings.jsx להוספת נתונים לסימון בצד השרת:

  1. בקובץ src/components/RestaurantListings.jsx, בודקים את הקוד הבא שכבר נכתב בשבילכם:
useEffect(() => {
    return getRestaurantsSnapshot((data) => {
      setRestaurants(data);
    }, filters);
  }, [filters]);

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

  1. בקובץ src/lib/firebase/firestore.js, מחליפים את הפונקציה getRestaurantsSnapshot() בקוד הבא:
export function getRestaurantsSnapshot(cb, filters = {}) {
  if (typeof cb !== "function") {
    console.log("Error: The callback parameter is not a function");
    return;
  }

  let q = query(collection(db, "restaurants"));
  q = applyQueryFilters(q, filters);

  return onSnapshot(q, (querySnapshot) => {
    const results = querySnapshot.docs.map((doc) => {
      return {
        id: doc.id,
        ...doc.data(),
        // Only plain objects can be passed to Client Components from Server Components
        timestamp: doc.data().timestamp.toDate(),
      };
    });

    cb(results);
  });
}
  1. בקובץ src/lib/firebase/firestore.js, מחליפים את הפונקציה getRestaurantSnapshotById() בקוד הבא:
export function getRestaurantSnapshotById(restaurantId, cb) {
  if (!restaurantId) {
    console.log("Error: Invalid ID received: ", restaurantId);
    return;
  }

  if (typeof cb !== "function") {
    console.log("Error: The callback parameter is not a function");
    return;
  }

  const docRef = doc(db, "restaurants", restaurantId);
  return onSnapshot(docRef, (docSnap) => {
    cb({
      ...docSnap.data(),
      timestamp: docSnap.data().timestamp.toDate(),
    });
  });
}

שינויים שבוצעו בדף Firestore Database משתקפים עכשיו באפליקציית האינטרנט בזמן אמת.

  1. יוצרים קומיט עם הודעת הקומיט Listen for realtime restaurant updates (האזנה לעדכונים בזמן אמת על מסעדות) ומבצעים push למאגר GitHub.
    git add .
    
    git commit -m "Listen for realtime restaurant updates"
    
    git push
    
  2. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  3. באפליקציית האינטרנט, בוחרים באפשרות 27ca5d1e8ed8adfe.png> הוספת מסעדות לדוגמה. אם פונקציית הצילום שלכם מיושמת בצורה נכונה, המסעדות יופיעו בזמן אמת בלי שתרעננו את הדף.

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

  1. בקובץ src/lib/firebase/firestore.js, מחליפים את הפונקציה updateWithRating() בקוד הבא:
const updateWithRating = async (
  transaction,
  docRef,
  newRatingDocument,
  review
) => {
  const restaurant = await transaction.get(docRef);
  const data = restaurant.data();
  const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1;
  const newSumRating = (data?.sumRating || 0) + Number(review.rating);
  const newAverage = newSumRating / newNumRatings;

  transaction.update(docRef, {
    numRatings: newNumRatings,
    sumRating: newSumRating,
    avgRating: newAverage,
  });

  transaction.set(newRatingDocument, {
    ...review,
    timestamp: Timestamp.fromDate(new Date()),
  });
};

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

  1. מחליפים את הפונקציה addReviewToRestaurant() בקוד הבא:
export async function addReviewToRestaurant(db, restaurantId, review) {
  if (!restaurantId) {
    throw new Error("No restaurant ID has been provided.");
  }

  if (!review) {
    throw new Error("A valid review has not been provided.");
  }

  try {
    const docRef = doc(collection(db, "restaurants"), restaurantId);
    const newRatingDocument = doc(
      collection(db, `restaurants/${restaurantId}/ratings`),
    );

    // corrected line
    await runTransaction(db, (transaction) =>
      updateWithRating(transaction, docRef, newRatingDocument, review),
    );
  } catch (error) {
    console.error(
      "There was an error adding the rating to the restaurant",
      error,
    );
    throw error;
  }
}

הטמעה של פעולת שרת ב-Next.js

פעולת שרת ב-Next.js מספקת API נוח לגישה לנתוני טופס, כמו data.get("text") כדי לקבל את ערך הטקסט ממטען הייעודי (payload) של שליחת הטופס.

כדי להשתמש ב-Next.js Server Action לעיבוד הטופס לשליחת ביקורת, פועלים לפי השלבים הבאים:

  1. בקובץ src/components/ReviewDialog.jsx, מחפשים את המאפיין action ברכיב <form>.
<form
  action={handleReviewFormSubmission}
  onSubmit={() => {
    handleClose();
  }}
>

ערך המאפיין action מתייחס לפונקציה שמטמיעים בשלב הבא.

  1. בקובץ src/app/actions.js, מחליפים את הפונקציה handleReviewFormSubmission() בקוד הבא:
export async function handleReviewFormSubmission(data) {
  const { firebaseServerApp } = await getAuthenticatedAppForUser();
  const db = getFirestore(firebaseServerApp);

  await addReviewToRestaurant(db, data.get("restaurantId"), {
    text: data.get("text"),
    rating: data.get("rating"),

    // This came from a hidden form field.
    userId: data.get("userId"),
  });
}

הוספת ביקורות על מסעדה

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

כדי להוסיף ביקורת ולוודא שהיא מוכנסת ל-Cloud Firestore, פועלים לפי השלבים הבאים:

  1. יוצרים קומיט עם הודעת הקומיט 'Allow users to submit restaurant reviews' (מאפשרים למשתמשים לשלוח ביקורות על מסעדות) ומבצעים push למאגר GitHub.
    git add .
    
    git commit -m "Allow users to submit restaurant reviews"
    
    git push
    
  2. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  3. מרעננים את אפליקציית האינטרנט ובוחרים מסעדה מדף הבית.
  4. בדף של המסעדה, לוחצים על 3e19beef78bb0d0e.png.
  5. בוחרים דירוג כוכבים.
  6. כתיבת ביקורת.
  7. לוחצים על שליחה. הביקורת שלכם תופיע בראש רשימת הביקורות.
  8. ב-Cloud Firestore, מחפשים בחלונית Add document את המסמך של המסעדה שכתבתם עליה ביקורת ובוחרים אותו.
  9. בחלונית התחלת איסוף, בוחרים באפשרות דירוגים.
  10. בחלונית הוספת מסמך, מחפשים את המסמך שרוצים לבדוק כדי לוודא שהוא הוכנס כמו שצריך.

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

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

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

  1. בקובץ src/components/Restaurant.jsx, בודקים את הקוד שמופעל כשהמשתמש מעלה קובץ:
async function handleRestaurantImage(target) {
  const image = target.files ? target.files[0] : null;
  if (!image) {
    return;
  }

  const imageURL = await updateRestaurantImage(id, image);
  setRestaurantDetails({ ...restaurantDetails, photo: imageURL });
}

לא צריך לבצע שינויים בפונקציה הזו, אבל בשלבים הבאים מוסבר איך מטמיעים את ההתנהגות של הפונקציה updateRestaurantImage().

  1. בקובץ src/lib/firebase/storage.js, מחליפים את הפונקציות updateRestaurantImage() ו-uploadImage() בקוד הבא:
export async function updateRestaurantImage(restaurantId, image) {
  try {
    if (!restaurantId) {
      throw new Error("No restaurant ID has been provided.");
    }

    if (!image || !image.name) {
      throw new Error("A valid image has not been provided.");
    }

    const publicImageUrl = await uploadImage(restaurantId, image);
    await updateRestaurantImageReference(restaurantId, publicImageUrl);

    return publicImageUrl;
  } catch (error) {
    console.error("Error processing request:", error);
  }
}

async function uploadImage(restaurantId, image) {
  const filePath = `images/${restaurantId}/${image.name}`;
  const newImageRef = ref(storage, filePath);
  await uploadBytesResumable(newImageRef, image);

  return await getDownloadURL(newImageRef);
}

הפונקציה updateRestaurantImageReference() כבר מיושמת בשבילכם. הפונקציה הזו מעדכנת מסמך קיים של מסעדה ב-Cloud Firestore באמצעות כתובת URL מעודכנת של תמונה.

אימות הפונקציונליות של העלאת תמונות

כדי לוודא שהתמונה מועלית כצפוי, פועלים לפי השלבים הבאים:

  1. יוצרים קומיט עם הודעת הקומיט 'Allow users to change each restaurants' photo' (מאפשרים למשתמשים לשנות את התמונה של כל מסעדה) ומבצעים push שלו למאגר ב-GitHub.
    git add .
    
    git commit -m "Allow users to change each restaurants' photo"
    
    git push
    
  2. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  3. באפליקציית האינטרנט, מוודאים שאתם מחוברים לחשבון ובוחרים מסעדה.
  4. לוחצים על 7067eb41fea41ff0.png ומעלים תמונה ממערכת הקבצים. התמונה יוצאת מהסביבה המקומית ומועלית ל-Cloud Storage. התמונה מופיעה מיד אחרי שמעלים אותה.
  5. עוברים אל Cloud Storage for Firebase.
  6. עוברים לתיקייה שמייצגת את המסעדה. התמונה שהעליתם קיימת בתיקייה.

6cf3f9e2303c931c.png

10. סיכום ביקורות על מסעדות באמצעות AI גנרטיבי

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

אחסון מפתח Gemini API ב-Cloud Secret Manager

  1. כדי להשתמש ב-Gemini API, תצטרכו מפתח API. נכנסים אל Google AI Studio ולוחצים על יצירת מפתח API.
  2. נותנים למפתח שם. אם הפרויקט לא מופיע בקטע בחירת פרויקט מיובא, לוחצים על ייבוא פרויקט, מסמנים את הפרויקט ברשימה ואז לוחצים על ייבוא. לבסוף, בוחרים אותו בקטע Choose an imported project ולוחצים על Create a key.
  3. שירות App Hosting משולב עם Cloud Secret Manager כדי לאפשר לכם לאחסן בצורה מאובטחת ערכים רגישים כמו מפתחות API:
    1. בטרמינל, מריצים את הפקודה ליצירת סוד חדש:
    firebase apphosting:secrets:set GEMINI_API_KEY
    
    1. כשמוצגת בקשה להזנת ערך הסוד, מעתיקים את מפתח Gemini API מ-Google AI Studio ומדביקים אותו.
    2. כשנשאלים אם הסוד החדש מיועד לסביבת הייצור או לבדיקה מקומית, בוחרים באפשרות 'סביבת הייצור'.
    3. כשמתבקשים לאשר גישה כדי שחשבון השירות של ה-backend יוכל לגשת לסוד, בוחרים באפשרות 'כן'.
    4. כשמוצגת השאלה אם להוסיף את הסוד החדש אל apphosting.yaml, מזינים Y כדי לאשר.

מפתח Gemini API מאוחסן עכשיו בצורה מאובטחת ב-Cloud Secret Manager, ויש לו גישה לקצה העורפי של App Hosting.

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

  1. ב-src/components/Reviews/ReviewSummary.jsx, מחליפים את הפונקציה GeminiSummary בקוד הבא:
    export async function GeminiSummary({ restaurantId }) {
      const { firebaseServerApp } = await getAuthenticatedAppForUser();
      const reviews = await getReviewsByRestaurantId(
        getFirestore(firebaseServerApp),
        restaurantId
      );
    
      const reviewSeparator = "@";
      const prompt = `
        Based on the following restaurant reviews, 
        where each review is separated by a '${reviewSeparator}' character, 
        create a one-sentence summary of what people think of the restaurant. 
    
        Here are the reviews: ${reviews.map((review) => review.text).join(reviewSeparator)}
      `;
    
      try {
        if (!process.env.GEMINI_API_KEY) {
          // Make sure GEMINI_API_KEY environment variable is set:
          // https://firebase.google.com/docs/genkit/get-started
          throw new Error(
            'GEMINI_API_KEY not set. Set it with "firebase apphosting:secrets:set GEMINI_API_KEY"'
          );
        }
    
        // Configure a Genkit instance.
        const ai = genkit({
          plugins: [googleAI()],
          model: gemini20Flash, // set default model
        });
        const { text } = await ai.generate(prompt);
    
        return (
          <div className="restaurant__review_summary">
            <p>{text}</p>
            <p> Summarized with Gemini</p>
          </div>
        );
      } catch (e) {
        console.error(e);
        return <p>Error summarizing reviews.</p>;
      }
    }
    
  2. יוצרים קומיט עם הודעת הקומיט Use AI to summarize reviews (שימוש ב-AI לסיכום ביקורות) ומבצעים push שלו למאגר GitHub.
    git add .
    
    git commit -m "Use AI to summarize reviews"
    
    git push
    
  3. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  4. פותחים דף של מסעדה. למעלה אמור להופיע סיכום של כל הביקורות בדף במשפט אחד.
  5. מוסיפים ביקורת חדשה ומרעננים את הדף. סיכום השינוי אמור להופיע.

11. ביטול הפרסום של האתר באירוח אפליקציות

אחרי שתסיימו את ה-codelab הזה, אם לא תמשיכו להשתמש באפליקציה, תוכלו לבטל את הפרסום שלה כדי לוודא שאף אחד לא יוכל לגשת למשאבי Firestore,‏ Storage ו-Gemini שלכם. תמיד אפשר לפרסם מחדש.

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

  1. פותחים את אירוח אפליקציות במסוף Firebase.
  2. מאתרים את העורף של האפליקציה ולוחצים על הצגה.
  3. בקטע Backend information (פרטי השרת), לצד Domains (דומיינים), לוחצים על Manage (ניהול). הדף Domains (דומיינים) ייטען.
  4. לצד הדומיין, לוחצים על סמל האפשרויות הנוספות (שלוש נקודות אנכיות), בוחרים באפשרות השבתת הדומיין ואז לוחצים על השבתה כדי לאשר.

12. סיכום

כל הכבוד! למדתם איך להשתמש ב-Firebase כדי להוסיף תכונות ופונקציונליות לאפליקציית Next.js. בפרט, השתמשתם ברכיבים הבאים:

  • Firebase App Hosting כדי ליצור ולפרוס באופן אוטומטי את קוד Next.js בכל פעם שמעלים קוד לענף מוגדר.
  • אימות ב-Firebase כדי להפעיל את הפונקציונליות של כניסה ויציאה.
  • Cloud Firestore לנתוני מסעדות ולנתוני ביקורות על מסעדות.
  • Cloud Storage for Firebase לתמונות של מסעדות.

מידע נוסף