Codelab בנושא Firebase Angular Web Frameworks

1. מה תיצרו

בשיעור הזה תלמדו איך ליצור בלוג על טיולים עם מפה שיתופית בזמן אמת, באמצעות הספרייה העדכנית שלנו ל-Angular: ‏ AngularFire. אפליקציית האינטרנט הסופית תכלול בלוג על טיולים, שבו תוכלו להעלות תמונות לכל מיקום שטיילתם בו.

האפליקציה תפותח באמצעות AngularFire, בדיקות מקומיות יבוצעו באמצעות Emulator Suite, נתוני המשתמשים יעקבו באמצעות Authentication, נתונים ומדיה יישמרו באמצעות Firestore ו-Storage (שמופעלים באמצעות Cloud Functions), ולבסוף, האפליקציה תופעל באמצעות Firebase Hosting.

מה תלמדו

  • איך מפתחים באמצעות מוצרי Firebase באופן מקומי באמצעות Emulator Suite
  • איך משפרים את אפליקציית האינטרנט באמצעות AngularFire
  • איך לשמור את הנתונים ב-Firestore
  • איך שומרים מדיה ב-Storage
  • איך פורסים את האפליקציה ב-Firebase Hosting
  • איך משתמשים ב-Cloud Functions כדי ליצור אינטראקציה עם מסדי נתונים וממשקי API

מה צריך להכין

  • Node.js בגרסה 10 ואילך
  • חשבון Google ליצירה ולניהול של פרויקט Firebase
  • Firebase CLI גרסה 11.14.2 ואילך
  • דפדפן לפי בחירתכם, כמו Chrome
  • הבנה בסיסית של Angular ו-JavaScript

2. קבלת קוד לדוגמה

משכפלים את מאגר הנתונים של GitHub של ה-codelab משורת הפקודה:

git clone https://github.com/firebase/codelab-friendlychat-web

לחלופין, אם לא התקנתם את git, אתם יכולים להוריד את המאגר כקובץ ZIP.

מאגר GitHub מכיל פרויקטים לדוגמה למספר פלטפורמות.

ב-codelab הזה נעשה שימוש רק במאגר webframework:

  • ‫📁 webframework: קוד ההתחלה שעליו תבנו במהלך ה-Codelab הזה.

התקנת יחסי תלות

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

cd webframework && npm install
cd functions && npm install

התקנת Firebase CLI

מתקינים את Firebase CLI באמצעות הפקודה הבאה בטרמינל:

npm install -g firebase-tools

כדי לוודא שגרסת Firebase CLI שלכם גדולה מ-11.14.2, משתמשים בפקודה:

firebase  --version

אם הגרסה שלכם נמוכה מ-11.14.2, אתם צריכים לעדכן אותה באמצעות:

npm update firebase-tools

3. יצירה והגדרה של פרויקט Firebase

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

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

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

  1. לוחצים על סמל האינטרנט כדי ליצור אפליקציית אינטרנט חדשה ב-Firebase.
  2. בשלב הבא יוצג אובייקט הגדרה. מעתיקים את התוכן של האובייקט הזה לקובץ environments/environment.ts.

הגדרת מוצרי Firebase

האפליקציה שאנחנו הולכים לבנות משתמשת במוצרי Firebase שזמינים לאפליקציות אינטרנט:

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

חלק מהמוצרים האלה דורשים הגדרות מיוחדות או הפעלה באמצעות מסוף Firebase.

הפעלת כניסה באמצעות חשבון Google לאימות ב-Firebase

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

כדי להפעיל כניסה באמצעות Google:

  1. במסוף Firebase, מאתרים את הקטע Build בחלונית הימנית.
  2. לוחצים על אימות ואז על הכרטיסייה שיטת כניסה (או לוחצים כאן כדי לעבור ישירות לשם).
  3. מפעילים את ספק הכניסה Google ולוחצים על שמירה.
  4. מגדירים את השם של האפליקציה שמוצג לציבור כ-<your-project-name> ובוחרים כתובת אימייל לתמיכה בפרויקט מהתפריט הנפתח.

הפעלת Cloud Firestore

  1. בקטע Build (פיתוח) במסוף Firebase, לוחצים על Firestore Database (מסד נתונים של Firestore).
  2. לוחצים על יצירת מסד נתונים בחלונית Cloud Firestore.
  3. הגדרת המיקום שבו הנתונים של Cloud Firestore מאוחסנים. אפשר להשאיר את ברירת המחדל או לבחור אזור שקרוב אליכם.

הפעלת Cloud Storage

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

  1. בקטע Build (פיתוח) במסוף Firebase, לוחצים על Storage (אחסון).
  2. אם לא מופיע הלחצן תחילת העבודה, המשמעות היא ש-Cloud Storage כבר

מופעל, ולא צריך לפעול לפי השלבים שבהמשך.

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

עם כללי האבטחה שמוגדרים כברירת מחדל, כל משתמש מאומת יכול לכתוב כל דבר ב-Cloud Storage. בהמשך ה-codelab נדבר על שיפור האבטחה של האחסון.

4. קישור לפרויקט Firebase

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

מוודאים ששורת הפקודה ניגשת לספרייה המקומית של האפליקציה webframework.

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

firebase login

לאחר מכן מריצים את הפקודה הבאה כדי ליצור כינוי לפרויקט. מחליפים את $YOUR_PROJECT_ID במזהה פרויקט Firebase.

firebase  use  $YOUR_PROJECT_ID

הוספת AngularFire

כדי להוסיף את AngularFire לאפליקציה, מריצים את הפקודה:

ng add @angular/fire

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

הפעלה של Firebase

כדי לאתחל את פרויקט Firebase, מריצים את הפקודה:

firebase init

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

הפעלת האמולטורים

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

firebase  emulators:start

בסופו של דבר אמור להופיע משהו כזה:

$  firebase  emulators:start

i  emulators:  Starting  emulators:  auth,  functions,  firestore,  hosting,  functions

i  firestore:  Firestore  Emulator  logging  to  firestore-debug.log

i  hosting:  Serving  hosting  files  from:  public

  hosting:  Local  server:  http://localhost:5000

i  ui:  Emulator  UI  logging  to  ui-debug.log

i  functions:  Watching  "/functions"  for  Cloud  Functions...

  functions[updateMap]:  firestore  function  initialized.

  

┌─────────────────────────────────────────────────────────────┐

    All  emulators  ready!  It  is  now  safe  to  connect  your  app.  

  i  View  Emulator  UI  at  http://localhost:4000  

└─────────────────────────────────────────────────────────────┘

  

┌────────────────┬────────────────┬─────────────────────────────────┐

  Emulator    Host:Port    View  in  Emulator  UI  

├────────────────┼────────────────┼─────────────────────────────────┤

  Authentication    localhost:9099    http://localhost:4000/auth  

├────────────────┼────────────────┼─────────────────────────────────┤

  Functions    localhost:5001    http://localhost:4000/functions  

├────────────────┼────────────────┼─────────────────────────────────┤

  Firestore    localhost:8080    http://localhost:4000/firestore  

├────────────────┼────────────────┼─────────────────────────────────┤

  Hosting    localhost:5000    n/a  

└────────────────┴────────────────┴─────────────────────────────────┘

Emulator  Hub  running  at  localhost:4400

Other  reserved  ports:  4500

  

Issues?  Report  them  at  https://github.com/firebase/firebase-tools/issues  and  attach  the  *-debug.log  files.

אחרי שמופיעה ההודעה ✔All emulators ready!, האמולטורים מוכנים לשימוש.

אמור להופיע ממשק המשתמש של אפליקציית הנסיעות, שלא פועל (עדיין):

עכשיו נתחיל לבנות!

5. חיבור אפליקציית האינטרנט לאמולטורים

על סמך הטבלה ביומני האמולטור, אמולטור Cloud Firestore מאזין ליציאה 8080 ואמולטור האימות מאזין ליציאה 9099.

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

בדפדפן האינטרנט, עוברים לכתובת http://127.0.0.1:4000/‎. אמור להופיע ממשק המשתמש של Emulator Suite.

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

ב-src/app/app.module.ts, מוסיפים את הקוד הבא לרשימת הייבוא של AppModule:

@NgModule({
	declarations: [...],
	imports: [
		provideFirebaseApp(() =>  initializeApp(environment.firebase)),

		provideAuth(() => {
			const  auth = getAuth();
			if (location.hostname === 'localhost') {
				connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings:  true });
			}
			return  auth;
		}),

		provideFirestore(() => {
			const  firestore = getFirestore();
			if (location.hostname === 'localhost') {
				connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
			}
			return  firestore;
		}),

		provideFunctions(() => {
			const  functions = getFunctions();
			if (location.hostname === 'localhost') {
				connectFunctionsEmulator(functions, '127.0.0.1', 5001);
			}
			return  functions;
		}),

		provideStorage(() => {
			const  storage = getStorage();
			if (location.hostname === 'localhost') {
				connectStorageEmulator(storage, '127.0.0.1', 5001);
			}
			return  storage;
		}),
		...
	]

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

6. הוספת אימות

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

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

החדרה של AngularFire Auth

ב-src/app/pages/login-page/login-page.component.ts, מייבאים את Auth מ-@angular/fire/auth ומזריקים אותו ל-LoginPageComponent. אפשר גם לייבא ישירות מהחבילה ספקי אימות, כמו Google, ופונקציות כמוsignin, signout ולהשתמש בהם באפליקציה.

import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from  '@angular/fire/auth';

export  class  LoginPageComponent  implements  OnInit {
	private  auth: Auth = inject(Auth);
	private  provider = new  GoogleAuthProvider();
	user$ = user(this.auth);
	constructor() {}  

	ngOnInit(): void {} 

	login() {
		signInWithPopup(this.auth, this.provider).then((result) => {
			const  credential = GoogleAuthProvider.credentialFromResult(result);
			return  credential;
		})
	}

	logout() {
		signOut(this.auth).then(() => {
			console.log('signed out');}).catch((error) => {
				console.log('sign out error: ' + error);
		})
	}
}

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

7. הגדרת Firestore

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

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

הטמעה של TravelService

מכיוון שדפים רבים ושונים יצטרכו לקרוא ולעדכן מסמכי Firestore באפליקציית האינטרנט, אפשר להטמיע את הפונקציות ב-src/app/services/travel.service.ts כדי להימנע מהזרקה חוזרת של אותן פונקציות AngularFire בכל דף.

מתחילים בהחדרת Auth, בדומה לשלב הקודם, וגם Firestore לשירות שלנו. כדאי גם להגדיר אובייקט user$ שניתן לצפייה בו, שמקשיב לסטטוס האימות הנוכחי.

import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from  "@angular/fire/firestore";

export  class  TravelService {
	firestore: Firestore = inject(Firestore);
	auth: Auth = inject(Auth);
	user$ = authState(this.auth).pipe(filter(user  =>  user !== null), map(user  =>  user!));
	router: Router = inject(Router);

הוספת פוסט על נסיעה

פוסטים בנושא נסיעות יופיעו כמסמכים שמאוחסנים ב-Firestore, ומכיוון שמסמכים צריכים להיות באוספים, האוסף שמכיל את כל הפוסטים בנושא נסיעות ייקרא travels. לכן, הנתיב של כל פוסט בנושא נסיעות יהיה travels/

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

async  addEmptyTravel(userId: String) {
	...
	addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
		collection(this.firestore, `travels/${travelRef.id}/stops`);
		setDoc(travelRef, {... travelData, id:  travelRef.id})
		this.router.navigate(['edit', `${travelRef.id}`]);
		return  travelRef;

	})
}

עדכון ומחיקה של נתונים

בהינתן ה-uid של פוסט כלשהו בנושא נסיעות, אפשר להסיק את הנתיב של המסמך שמאוחסן ב-Firestore, ואז לקרוא, לעדכן או למחוק אותו באמצעות הפונקציות updateFoc ו-deleteDoc של AngularFire:

async  updateData(path: string, data: Partial<Travel | Stop>) {
	await  updateDoc(doc(this.firestore, path), data)
}

async  deleteData(path: string) {
	const  ref = doc(this.firestore, path);
	await  deleteDoc(ref)
}

קריאת נתונים כ-Observable

מכיוון שאפשר לשנות פוסטים על נסיעות ותחנות בדרך אחרי שהם נוצרים, יהיה שימושי יותר לקבל אובייקטים של מסמכים כמשתנים שניתנים לצפייה, כדי להירשם לכל שינוי שמתבצע. הפונקציונליות הזו מוצעת על ידי הפונקציות docData ו-collectionData מ-@angular/fire/firestore.

getDocData(path: string) {
	return  docData(doc(this.firestore, path), {idField:  'id'}) as  Observable<Travel | Stop>
}

  
getCollectionData(path: string) {
	return  collectionData(collection(this.firestore, path), {idField:  'id'}) as  Observable<Travel[] | Stop[]>
}

הוספת עצירות לפוסט על נסיעה

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

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

async  addStop(travelId: string) {
	...
	const  ref = await  addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
	setDoc(ref, {...stopData, id:  ref.id})
}

איזה יופי! הפונקציות של Firestore הוטמעו בשירות Travel, כך שעכשיו אפשר לראות אותן בפעולה.

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

כדי להשתמש בפונקציות של TravelService, עוברים אל src/app/pages/my-travels/my-travels.component.ts ומזריקים את TravelService.

travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
	this.travelsData$ = this.travelService.getCollectionData(`travels`) as  Observable<Travel[]>
}

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

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

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

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

async  createTravel(userId: String) {
	this.travelService.addEmptyTravel(userId);
}

deleteTravel(travelId: String) {
	this.travelService.deleteData(`travels/${travelId}`)
}

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

לאחר מכן, חוזרים על הפעולה עבור פונקציות העדכון ב-/src/app/pages/edit-travels/edit-travels.component.ts :

travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;

constructor() {
	this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as  Observable<Travel>
	this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as  Observable<Stop[]>
}

updateCurrentTravel(travel: Partial<Travel>) {
	this.travelService.updateData(`travels${this.travelId}`, travel)
}

  

updateCurrentStop(stop: Partial<Stop>) {
	stop.type = stop.type?.toString();
	this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}

  

addStop() {
	if (!this.travelId) return;
	this.travelService.addStop(this.travelId);
}

deleteStop(stopId: string) {
	if (!this.travelId || !stopId) {
		return;
	}
	this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
	this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as  Observable<Stop[]>

}

8. הגדרת אחסון

עכשיו תטמיעו את Storage כדי לאחסן תמונות וסוגים אחרים של מדיה.

השימוש ב-Cloud Firestore מומלץ לאחסון נתונים מובְנים, כמו אובייקטים בפורמט JSON. ‫Cloud Storage מיועד לאחסון קבצים או בלובים. באפליקציה הזו, תשתמשו ב-API כדי לאפשר למשתמשים לשתף תמונות מהטיולים שלהם.

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

עכשיו נטמיע את הפונקציות ב-TraveService:

העלאת קובץ

עוברים אל src/app/services/travel.service.ts ומזריקים אחסון מ-AngularFire:

export  class  TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);

ומטמיעים את פונקציית ההעלאה:

async  uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
	if (!input.files) return  null
	const  files: FileList = input.files;
		for (let  i = 0; i  <  files.length; i++) {
			const  file = files.item(i);
			if (file) {
				const  imagePath = `${path}/${file.name}`
				const  storageRef = ref(this.storage, imagePath);
				await  uploadBytesResumable(storageRef, file, contentType);
				return  await  getDownloadURL(storageRef);
			}
		}
	return  null;
}

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

שימוש בפונקציה באפליקציה

עוברים אל src/app/components/edit-stop/edit-stop.component.ts ומפעילים את פונקציית ההעלאה באמצעות:

	async  uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
	const  path = `/travels/${this.travelId}/stops/${stop.id}`
	const  url = await  this.travelService.uploadToStorage(path, file, {contentType:  'image/png'});
	stop.image = url ? url : '';
	this.travelService.updateData(path, stop);
}

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

9. פריסת האפליקציה

עכשיו אפשר לפרוס את האפליקציה.

מעתיקים את ההגדרות של firebase מ-src/environments/environment.ts אל src/environments/environment.prod.ts ומריצים:

firebase deploy

אתם אמורים לראות משהו כזה:

 Browser application bundle generation complete.
 Copying assets complete.
 Index html generation complete.

=== Deploying to 'friendly-travels-b6a4b'...

i  deploying storage, firestore, hosting
i  firebase.storage: checking storage.rules for compilation errors...
  firebase.storage: rules file storage.rules compiled successfully
i  firestore: reading indexes from firestore.indexes.json...
i  cloud.firestore: checking firestore.rules for compilation errors...
  cloud.firestore: rules file firestore.rules compiled successfully
i  storage: latest version of storage.rules already up to date, skipping upload...
i  firestore: deploying indexes...
i  firestore: latest version of firestore.rules already up to date, skipping upload...
  firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i  hosting[friendly-travels-b6a4b]: beginning deploy...
i  hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
  hosting[friendly-travels-b6a4b]: file upload complete
  storage: released rules storage.rules to firebase.storage
  firestore: released rules firestore.rules to cloud.firestore
i  hosting[friendly-travels-b6a4b]: finalizing version...
  hosting[friendly-travels-b6a4b]: version finalized
i  hosting[friendly-travels-b6a4b]: releasing new version...
  hosting[friendly-travels-b6a4b]: release complete

  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app

10. כל הכבוד!

האפליקציה שלכם אמורה להיות מוכנה ומוצגת ב-Firebase Hosting. כל הנתונים והניתוחים יהיו עכשיו זמינים במסוף Firebase.

כדי לקבל מידע על תכונות נוספות שקשורות ל-AngularFire, ל-Functions ולכללי אבטחה, מומלץ לעיין בשלבים האופציונליים שבהמשך, וגם בסדנאות התכנות (Codelabs) האחרות של Firebase.

11. אופציונלי: AngularFire auth guards

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

ב-src/app/app-routing.module.ts, מייבאים

import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from  '@angular/fire/auth-guard'

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

const  redirectUnauthorizedToLogin = () =>  redirectUnauthorizedTo(['signin']);
const  redirectLoggedInToTravels = () =>  redirectLoggedInTo(['my-travels']);

אחר כך פשוט מוסיפים אותם למסלולים:

const  routes: Routes = [
	{path:  '', component:  LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectLoggedInToTravels}},
	{path:  'signin', component:  LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectLoggedInToTravels}},
	{path:  'my-travels', component:  MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectUnauthorizedToLogin}},
	{path:  'edit/:travelId', component:  EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectUnauthorizedToLogin}},
];

12. אופציונלי: כללי אבטחה

גם ב-Firestore וגם ב-Cloud Storage נעשה שימוש בכללי אבטחה (firestore.rules ו-security.rules בהתאמה) כדי לאכוף אבטחה ולאמת נתונים.

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

כללי Firestore

כדי לאפשר רק למשתמשים מאומתים לצפות בפוסטים על נסיעות, עוברים לקובץ firestore.rules ומוסיפים:

rules_version  =  '2';
service  cloud.firestore  {
	match  /databases/{database}/travels  {
		allow  read:  if  request.auth.uid  !=  null;
		allow  write:
		if  request.auth.uid  ==  request.resource.data.userId;
	}
}

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

rules_version  =  '2';
service  cloud.firestore  {
	match  /databases/{database}/posts  {
		allow  read:  if  request.auth.uid  !=  null;
		allow  write:
		if  request.auth.uid  ==  request.resource.data.userId;
		&&  "author"  in  request.resource.data
		&&  "text"  in  request.resource.data
		&&  "timestamp"  in  request.resource.data;
	}
}

כללי אחסון

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

rules_version  =  '2';

function  isImageBelowMaxSize(maxSizeMB)  {
	return  request.resource.size  <  maxSizeMB  *  1024  *  1024
		&&  request.resource.contentType.matches('image/.*');
}

 service  firebase.storage  {
	match  /b/{bucket}/o  {
		match  /{userId}/{postId}/{filename}  {
			allow  write:  if  request.auth  !=  null
			&&  request.auth.uid  ==  userId  &&  isImageBelowMaxSize(5);
			allow  read;
		}
	}
}