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

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

ב-Codelab הזה נלמד איך לשלב את 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

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

firebase --version

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

npm install -g firebase-tools@latest

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

מתחברים ל-Firebase

  1. מריצים את הפקודה הבאה כדי להתחבר ל-Firebase CLI:
    firebase login
    
  2. בהתאם לשאלה אם רוצים ש-Firebase יאסוף נתונים, מזינים Y או N.
  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.

  • בחשבון לחיוב ב-Cloud צריך להגדיר אמצעי תשלום, כמו כרטיס אשראי.
  • אם אתם חדשים ב-Firebase וב-Google Cloud, כדאי לבדוק אם אתם עומדים בדרישות לקבלת קרדיט בשווי 300$ותקופת ניסיון בחינם בחשבון לחיוב ב-Cloud.
  • אם אתם משתתפים ב-Codelab כחלק מאירוע, כדאי לשאול את מארגן האירוע אם יש קרדיטים ל-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, תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אסור להפיץ או לחשוף אפליקציה באופן ציבורי בלי להוסיף כללי אבטחה לדלי האחסון.
  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. בדיקת בסיס הקוד ההתחלתי

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

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

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

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

תיאור

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. יצירת App Hosting קצה עורפי

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

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

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

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

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

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

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

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

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

  1. פותחים את הדף App Hosting (אירוח אפליקציות) ולוחצים על View (הצגה) מתחת לאתר שהופעל כדי לגשת לדף Overview (סקירה כללית). מעתיקים את שם הדומיין של העורף (backend) של האפליקציה שמתארחת ב-App Hosting.
  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. בחלונית Start collection, בוחרים באפשרות ratings.
  10. בחלונית הוספת מסמך, מחפשים את המסמך שרוצים לבדוק כדי לוודא שהוא הוכנס כמו שצריך.

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

בקטע הזה, מוסיפים פונקציונליות שתאפשר להחליף את התמונה שמשויכת למסעדה כשמחוברים לחשבון. מעלים את התמונה לאחסון ב-Firebase, ומעדכנים את כתובת ה-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 ולוחצים על Create API Key (יצירת מפתח 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://genkit.dev/docs/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. יוצרים קומיט עם הודעת הקומיט 'שימוש ב-AI לסיכום ביקורות' ומבצעים push למאגר GitHub.
    git add .
    
    git commit -m "Use AI to summarize reviews"
    
    git push
    
  3. פותחים את הדף App Hosting במסוף Firebase ומחכים עד שההשקה החדשה תושלם.
  4. פותחים דף של מסעדה. למעלה אמור להופיע סיכום של כל הביקורות בדף במשפט אחד.
  5. מוסיפים ביקורת חדשה ומרעננים את הדף. סיכום השינוי אמור להופיע.

11. ביטול הפרסום של האתר ב-App Hosting

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

כדי לבטל את הפרסום של אתר ב-App Hosting:

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

12. סיכום

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

מידע נוסף