Codelab ในเว็บ Cloud Firestore

1. ภาพรวม

เป้าหมาย

ในโค้ดแล็บนี้ คุณจะได้สร้างเว็บแอปแนะนำร้านอาหารที่ขับเคลื่อนโดย Cloud Firestore

img5.png

สิ่งที่คุณจะได้เรียนรู้

  • อ่านและเขียนข้อมูลไปยัง Cloud Firestore จากเว็บแอป
  • ฟังการเปลี่ยนแปลงข้อมูล Cloud Firestore แบบเรียลไทม์
  • ใช้การตรวจสอบสิทธิ์ของ Firebase และกฎการรักษาความปลอดภัยเพื่อรักษาความปลอดภัยของข้อมูล Cloud Firestore
  • เขียนการค้นหา Cloud Firestore ที่ซับซ้อน

สิ่งที่คุณต้องมี

โปรดตรวจสอบว่าคุณได้ติดตั้งสิ่งต่อไปนี้ก่อนเริ่ม Codelab นี้

  • npm ซึ่งมักจะมาพร้อมกับ Node.js - ขอแนะนำให้ใช้ Node 16 ขึ้นไป
  • IDE/โปรแกรมแก้ไขข้อความที่คุณเลือก เช่น WebStorm, VS Code หรือ Sublime

2. สร้างและตั้งค่าโปรเจ็กต์ Firebase

สร้างโปรเจ็กต์ Firebase

  1. ลงชื่อเข้าใช้คอนโซล Firebase โดยใช้บัญชี Google
  2. คลิกปุ่มเพื่อสร้างโปรเจ็กต์ใหม่ แล้วป้อนชื่อโปรเจ็กต์ (เช่น FriendlyEats)
  3. คลิกต่อไป
  4. หากได้รับแจ้ง ให้อ่านและยอมรับข้อกำหนดของ Firebase แล้วคลิกต่อไป
  5. (ไม่บังคับ) เปิดใช้ความช่วยเหลือจาก AI ในคอนโซล Firebase (เรียกว่า "Gemini ใน Firebase")
  6. สำหรับ Codelab นี้ คุณไม่จำเป็นต้องใช้ Google Analytics ดังนั้นให้ปิดตัวเลือก Google Analytics
  7. คลิกสร้างโปรเจ็กต์ รอให้ระบบจัดสรรโปรเจ็กต์ แล้วคลิกดำเนินการต่อ

ตั้งค่าผลิตภัณฑ์ Firebase

แอปพลิเคชันที่เราจะสร้างใช้บริการ Firebase บางอย่างที่มีให้บริการบนเว็บ ดังนี้

  • การตรวจสอบสิทธิ์ Firebase เพื่อระบุผู้ใช้ได้อย่างง่ายดาย
  • Cloud Firestore เพื่อบันทึกข้อมูลที่มีโครงสร้างไว้ในระบบคลาวด์และรับการแจ้งเตือนทันทีเมื่อมีการอัปเดตข้อมูล
  • Firebase Hosting เพื่อโฮสต์และแสดงเนื้อหาแบบคงที่

สำหรับโค้ดแล็บนี้ เราได้กำหนดค่าโฮสติ้งของ Firebase ไว้แล้ว อย่างไรก็ตาม สำหรับ Firebase Auth และ Cloud Firestore เราจะแนะนำการกำหนดค่าและการเปิดใช้บริการโดยใช้คอนโซล Firebase

เปิดใช้การตรวจสอบสิทธิ์แบบไม่ระบุชื่อ

แม้ว่าการตรวจสอบสิทธิ์จะไม่ใช่จุดสนใจของโค้ดแล็บนี้ แต่การมีรูปแบบการตรวจสอบสิทธิ์ในแอปก็เป็นสิ่งสำคัญ เราจะใช้การเข้าสู่ระบบแบบไม่ระบุตัวตน ซึ่งหมายความว่าระบบจะลงชื่อเข้าใช้ผู้ใช้โดยอัตโนมัติโดยไม่ต้องแจ้งให้ทราบ

คุณจะต้องเปิดใช้การเข้าสู่ระบบแบบไม่ระบุชื่อ

  1. ในคอนโซล Firebase ให้หาส่วนสร้างในการนำทางด้านซ้าย
  2. คลิก Authentication แล้วคลิกแท็บวิธีการลงชื่อเข้าใช้ (หรือคลิกที่นี่เพื่อไปที่แท็บดังกล่าวโดยตรง)
  3. เปิดใช้ผู้ให้บริการลงชื่อเข้าใช้แบบไม่ระบุชื่อ แล้วคลิกบันทึก

img7.png

ซึ่งจะช่วยให้แอปพลิเคชันลงชื่อเข้าใช้ผู้ใช้โดยอัตโนมัติเมื่อผู้ใช้เข้าถึงเว็บแอป โปรดอ่านเอกสารประกอบการตรวจสอบสิทธิ์แบบไม่ระบุตัวตนเพื่อดูข้อมูลเพิ่มเติม

เปิดใช้ Cloud Firestore

แอปใช้ Cloud Firestore เพื่อบันทึกและรับข้อมูลและคะแนนของร้านอาหาร

คุณจะต้องเปิดใช้ Cloud Firestore ในส่วนสร้างของคอนโซล Firebase ให้คลิกฐานข้อมูล Firestore คลิกสร้างฐานข้อมูลในแผง Cloud Firestore

การเข้าถึงข้อมูลใน Cloud Firestore จะควบคุมโดยกฎความปลอดภัย เราจะพูดถึงกฎเพิ่มเติมใน Codelab นี้ในภายหลัง แต่ก่อนอื่นเราต้องตั้งกฎพื้นฐานบางอย่างในข้อมูลเพื่อเริ่มต้น ในแท็บกฎของคอนโซล Firebase ให้เพิ่มกฎต่อไปนี้ แล้วคลิกเผยแพร่

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data)
      && (key in request.resource.data)
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && unchanged("name");

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

เราจะพูดถึงกฎเหล่านี้และวิธีการทำงานในภายหลังใน Codelab

3. รับโค้ดตัวอย่าง

โคลนที่เก็บ GitHub จากบรรทัดคำสั่งโดยใช้คำสั่งต่อไปนี้

git clone https://github.com/firebase/friendlyeats-web

ระบบควรโคลนโค้ดตัวอย่างลงในไดเรกทอรี 📁friendlyeats-web จากนี้ไป ให้เรียกใช้คำสั่งทั้งหมดจากไดเรกทอรีนี้

cd friendlyeats-web/vanilla-js

นำเข้าแอปเริ่มต้น

ใช้ IDE (WebStorm, Atom, Sublime, Visual Studio Code...) เพื่อเปิดหรือนำเข้าไดเรกทอรี 📁friendlyeats-web ไดเรกทอรีนี้มีโค้ดเริ่มต้นสำหรับโค้ดแล็บ ซึ่งประกอบด้วยแอปแนะนำร้านอาหารที่ยังใช้งานไม่ได้ เราจะทำให้แอปใช้งานได้ตลอดทั้งโค้ดแล็บนี้ ดังนั้นคุณจะต้องแก้ไขโค้ดในไดเรกทอรีนั้นในเร็วๆ นี้

4. ติดตั้งอินเทอร์เฟซบรรทัดคำสั่งของ Firebase

อินเทอร์เฟซบรรทัดคำสั่ง (CLI) ของ Firebase ช่วยให้คุณแสดงเว็บแอปในเครื่องและทำให้ใช้งานได้กับโฮสติ้งของ Firebase

  1. ติดตั้ง CLI โดยเรียกใช้คำสั่ง npm ต่อไปนี้
npm -g install firebase-tools
  1. ยืนยันว่าติดตั้ง CLI อย่างถูกต้องแล้วโดยเรียกใช้คำสั่งต่อไปนี้
firebase --version

ตรวจสอบว่า Firebase CLI เป็นเวอร์ชัน v7.4.0 ขึ้นไป

  1. ให้สิทธิ์ Firebase CLI โดยการเรียกใช้คำสั่งต่อไปนี้
firebase login

เราได้ตั้งค่าเทมเพลตเว็บแอปเพื่อดึงการกำหนดค่าของแอปสำหรับโฮสติ้งของ Firebase จากไดเรกทอรีและไฟล์ในเครื่องของแอป แต่ในการดำเนินการนี้ เราต้องเชื่อมโยงแอปกับโปรเจ็กต์ Firebase ของคุณ

  1. ตรวจสอบว่าบรรทัดคำสั่งเข้าถึงไดเรกทอรีภายในของแอป
  2. เชื่อมโยงแอปกับโปรเจ็กต์ Firebase โดยเรียกใช้คำสั่งต่อไปนี้
firebase use --add
  1. เมื่อได้รับข้อความแจ้ง ให้เลือกรหัสโปรเจ็กต์ แล้วตั้งนามแฝงให้โปรเจ็กต์ Firebase

นามแฝงมีประโยชน์หากคุณมีหลายสภาพแวดล้อม (การใช้งานจริง การทดลองใช้ ฯลฯ) แต่สำหรับ Codelab นี้ ให้เราใช้นามแฝงของ default

  1. ทำตามวิธีการที่เหลือในบรรทัดคำสั่ง

5. เรียกใช้เซิร์ฟเวอร์ภายใน

เราพร้อมที่จะเริ่มทำงานกับแอปแล้ว มาเรียกใช้แอปในเครื่องกันเลย

  1. เรียกใช้คำสั่ง Firebase CLI ต่อไปนี้
firebase emulators:start --only hosting
  1. บรรทัดคำสั่งควรแสดงการตอบกลับต่อไปนี้
hosting: Local server: http://localhost:5000

เราใช้โปรแกรมจำลอง Firebase Hosting เพื่อแสดงแอปในเครื่อง ตอนนี้เว็บแอปควรพร้อมใช้งานจาก http://localhost:5000 แล้ว

  1. เปิดแอปที่ http://localhost:5000

คุณควรเห็นสำเนาของ FriendlyEats ที่เชื่อมต่อกับโปรเจ็กต์ Firebase

แอปได้เชื่อมต่อกับโปรเจ็กต์ Firebase โดยอัตโนมัติและลงชื่อเข้าใช้คุณเป็นผู้ใช้ที่ไม่ระบุตัวตนโดยที่คุณไม่ทราบ

img2.png

6. เขียนข้อมูลไปยัง Cloud Firestore

ในส่วนนี้ เราจะเขียนข้อมูลบางอย่างลงใน Cloud Firestore เพื่อให้เราสามารถป้อนข้อมูลลงใน UI ของแอปได้ คุณทำได้ด้วยตนเองผ่านคอนโซล Firebase แต่เราจะทำในแอปเองเพื่อแสดงการเขียน Cloud Firestore ขั้นพื้นฐาน

โมเดลข้อมูล

ข้อมูล Firestore จะแบ่งออกเป็นคอลเล็กชัน เอกสาร ฟิลด์ และคอลเล็กชันย่อย เราจะจัดเก็บร้านอาหารแต่ละร้านเป็นเอกสารในคอลเล็กชันระดับบนสุดที่ชื่อ restaurants

img3.png

จากนั้นเราจะจัดเก็บรีวิวแต่ละรายการในคอลเล็กชันย่อยที่ชื่อ ratings ของร้านอาหารแต่ละร้าน

img4.png

เพิ่มร้านอาหารลงใน Firestore

ออบเจ็กต์โมเดลหลักในแอปของเราคือร้านอาหาร มาเขียนโค้ดเพื่อเพิ่มเอกสารร้านอาหารลงในคอลเล็กชัน restaurants กัน

  1. เปิด scripts/FriendlyEats.Data.js จากไฟล์ที่ดาวน์โหลด
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.addRestaurant
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

โค้ดด้านบนจะเพิ่มเอกสารใหม่ลงในrestaurantsคอลเล็กชัน ข้อมูลเอกสารมาจากออบเจ็กต์ JavaScript ธรรมดา เราทำเช่นนี้โดยเริ่มจากการรับการอ้างอิงไปยังคอลเล็กชัน Cloud Firestore restaurants จากนั้นจึงaddข้อมูล

มาเพิ่มร้านอาหารกัน

  1. กลับไปที่แอป FriendlyEats ในเบราว์เซอร์แล้วรีเฟรช
  2. คลิกเพิ่มข้อมูลจำลอง

แอปจะสร้างชุดออบเจ็กต์ร้านอาหารแบบสุ่มโดยอัตโนมัติ จากนั้นจะเรียกฟังก์ชัน addRestaurant อย่างไรก็ตาม คุณจะยังไม่เห็นข้อมูลในเว็บแอปจริง เนื่องจากเรายังต้องติดตั้งใช้งานการดึงข้อมูล (ส่วนถัดไปของโค้ดแล็บ)

อย่างไรก็ตาม หากไปที่แท็บ Cloud Firestore ในคอนโซล Firebase คุณควรเห็นเอกสารใหม่ในคอลเล็กชัน restaurants แล้ว

img6.png

ขอแสดงความยินดี คุณเพิ่งเขียนข้อมูลลงใน Cloud Firestore จากเว็บแอป

ในส่วนถัดไป คุณจะได้ดูวิธีดึงข้อมูลจาก Cloud Firestore และแสดงในแอป

7. แสดงข้อมูลจาก Cloud Firestore

ในส่วนนี้ คุณจะได้เรียนรู้วิธีดึงข้อมูลจาก Cloud Firestore และแสดงข้อมูลในแอป ขั้นตอนสำคัญ 2 อย่างคือการสร้างการค้นหาและการเพิ่มเครื่องมือฟังภาพรวม ผู้ฟังนี้จะได้รับการแจ้งเตือนเกี่ยวกับข้อมูลที่มีอยู่ทั้งหมดที่ตรงกับคำค้นหาและจะได้รับการอัปเดตแบบเรียลไทม์

ก่อนอื่น มาสร้างคำค้นหาที่จะแสดงรายการร้านอาหารเริ่มต้นที่ไม่มีการกรองกัน

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.getAllRestaurants
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

ในโค้ดด้านบน เราสร้างคําค้นหาที่จะดึงร้านอาหารได้สูงสุด 50 แห่งจากคอลเล็กชันระดับบนสุดที่ชื่อ restaurants ซึ่งจัดเรียงตามคะแนนเฉลี่ย (ปัจจุบันเป็น 0 ทั้งหมด) หลังจากประกาศการค้นหานี้แล้ว เราจะส่งไปยังเมธอด getDocumentsInQuery() ซึ่งมีหน้าที่โหลดและแสดงผลข้อมูล

เราจะดำเนินการนี้โดยการเพิ่มเครื่องมือฟังภาพรวม

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.getDocumentsInQuery
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

ในโค้ดด้านบน query.onSnapshot จะทริกเกอร์การเรียกกลับทุกครั้งที่มีการเปลี่ยนแปลงผลลัพธ์ของการค้นหา

  • ครั้งแรก ระบบจะทริกเกอร์การเรียกกลับด้วยชุดผลลัพธ์ทั้งหมดของคำค้นหา ซึ่งหมายถึงทั้งคอลเล็กชัน restaurants จาก Cloud Firestore จากนั้นจะส่งเอกสารแต่ละฉบับไปยังฟังก์ชัน renderer.display
  • เมื่อลบเอกสาร change.type จะเท่ากับ removed ดังนั้นในกรณีนี้ เราจะเรียกใช้ฟังก์ชันที่นำร้านอาหารออกจาก UI

ตอนนี้เราได้ใช้ทั้ง 2 วิธีแล้ว ให้รีเฟรชแอปและตรวจสอบว่าร้านอาหารที่เราเห็นก่อนหน้านี้ในคอนโซล Firebase ปรากฏในแอปแล้ว หากคุณทำส่วนนี้สำเร็จ แสดงว่าตอนนี้แอปของคุณอ่านและเขียนข้อมูลด้วย Cloud Firestore ได้แล้ว

เมื่อรายชื่อร้านอาหารเปลี่ยนแปลง ผู้ฟังนี้จะอัปเดตโดยอัตโนมัติ ลองไปที่คอนโซล Firebase แล้วลบร้านอาหารหรือเปลี่ยนชื่อด้วยตนเอง คุณจะเห็นการเปลี่ยนแปลงปรากฏในเว็บไซต์ทันที

img5.png

8. รับ() ข้อมูล

ที่ผ่านมาเราได้แสดงวิธีใช้ onSnapshot เพื่อดึงข้อมูลอัปเดตแบบเรียลไทม์แล้ว แต่บางครั้งเราก็ไม่ได้ต้องการแบบนั้น บางครั้งการดึงข้อมูลเพียงครั้งเดียวก็สมเหตุสมผลกว่า

เราจะต้องใช้วิธีการที่ทริกเกอร์เมื่อผู้ใช้คลิกร้านอาหารที่เฉพาะเจาะจงในแอป

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.getRestaurant
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

หลังจากใช้วิธีนี้แล้ว คุณจะดูหน้าเว็บของร้านอาหารแต่ละแห่งได้ เพียงคลิกร้านอาหารในรายการ คุณก็จะเห็นหน้ารายละเอียดของร้านอาหาร

img1.png

ตอนนี้คุณยังเพิ่มคะแนนไม่ได้ เนื่องจากเรายังต้องติดตั้งใช้งานการเพิ่มคะแนนในภายหลังใน Codelab

9. จัดเรียงและกรองข้อมูล

ปัจจุบันแอปของเราแสดงรายชื่อร้านอาหาร แต่ผู้ใช้ไม่สามารถกรองตามความต้องการได้ ในส่วนนี้ คุณจะได้ใช้การค้นหาขั้นสูงของ Cloud Firestore เพื่อเปิดใช้การกรอง

ต่อไปนี้คือตัวอย่างคำค้นหาอย่างง่ายเพื่อดึงข้อมูลร้านอาหาร Dim Sum ทั้งหมด

var filteredQuery = query.where('category', '==', 'Dim Sum')

ตามชื่อของฟังก์ชัน where() เมธอดจะทำให้การดาวน์โหลดคำค้นหาของเรามีเฉพาะสมาชิกของคอลเล็กชันที่มีช่องตรงตามข้อจำกัดที่เราตั้งไว้ ในกรณีนี้ ระบบจะดาวน์โหลดเฉพาะร้านอาหารที่ category เป็น Dim Sum

ในแอปของเรา ผู้ใช้สามารถเชื่อมโยงตัวกรองหลายรายการเพื่อสร้างคำค้นหาที่เฉพาะเจาะจง เช่น "พิซซ่าในซานฟรานซิสโก" หรือ "อาหารทะเลในลอสแอนเจลิสที่จัดเรียงตามความนิยม"

เราจะสร้างวิธีการที่สร้างคำค้นหาซึ่งจะกรองร้านอาหารตามเกณฑ์หลายอย่างที่ผู้ใช้เลือก

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.getFilteredRestaurants
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

โค้ดด้านบนจะเพิ่มwhereตัวกรองหลายรายการและorderByคําสั่งเดียวเพื่อสร้างการค้นหาแบบผสมตามอินพุตของผู้ใช้ ตอนนี้การค้นหาจะแสดงเฉพาะร้านอาหารที่ตรงกับความต้องการของผู้ใช้เท่านั้น

รีเฟรชแอป FriendlyEats ในเบราว์เซอร์ จากนั้นตรวจสอบว่าคุณกรองตามราคา เมือง และหมวดหมู่ได้ ขณะทดสอบ คุณจะเห็นข้อผิดพลาดใน JavaScript Console ของเบราว์เซอร์ที่มีลักษณะดังนี้

The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...

ข้อผิดพลาดเหล่านี้เกิดขึ้นเนื่องจาก Cloud Firestore ต้องใช้ดัชนีสำหรับการค้นหาแบบผสมส่วนใหญ่ การกำหนดให้ใช้ดัชนีในการค้นหาจะช่วยให้ Cloud Firestore ทำงานได้อย่างรวดเร็วเมื่อมีการปรับขนาด

การเปิดลิงก์จากข้อความแสดงข้อผิดพลาดจะเปิด UI การสร้างดัชนีในคอนโซล Firebase โดยอัตโนมัติพร้อมกับพารามิเตอร์ที่ถูกต้อง ในส่วนถัดไป เราจะเขียนและติดตั้งใช้งานดัชนีที่จำเป็นสำหรับแอปพลิเคชันนี้

10. ทำให้ดัชนีใช้งานได้

หากไม่ต้องการสำรวจทุกเส้นทางในแอปและติดตามลิงก์การสร้างดัชนีแต่ละลิงก์ เราก็สามารถติดตั้งใช้งานดัชนีหลายรายการพร้อมกันได้อย่างง่ายดายโดยใช้ Firebase CLI

  1. คุณจะเห็นไฟล์ firestore.indexes.json ในไดเรกทอรีในเครื่องที่ดาวน์โหลดของแอป

ไฟล์นี้อธิบายดัชนีทั้งหมดที่จำเป็นสำหรับชุดค่าผสมที่เป็นไปได้ทั้งหมดของตัวกรอง

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. โดยคุณสามารถติดตั้งใช้งานดัชนีเหล่านี้ได้ด้วยคำสั่งต่อไปนี้
firebase deploy --only firestore:indexes

หลังจากผ่านไป 2-3 นาที ดัชนีจะใช้งานได้และข้อความแสดงข้อผิดพลาดจะหายไป

11. เขียนข้อมูลในธุรกรรม

ในส่วนนี้ เราจะเพิ่มความสามารถให้ผู้ใช้ส่งรีวิวไปยังร้านอาหารได้ ที่ผ่านมา การเขียนทั้งหมดของเราเป็นแบบอะตอมมิกและค่อนข้างเรียบง่าย หากรายการใดมีข้อผิดพลาด เราอาจแจ้งให้ผู้ใช้ลองอีกครั้ง หรือแอปของเราจะลองเขียนอีกครั้งโดยอัตโนมัติ

แอปของเราจะมีผู้ใช้จำนวนมากที่ต้องการให้คะแนนร้านอาหาร ดังนั้นเราจึงต้องประสานงานการอ่านและการเขียนหลายรายการ โดยคุณต้องส่งรีวิวก่อน จากนั้นจึงอัปเดตคะแนนของร้านอาหาร count และ average rating หากการดำเนินการอย่างใดอย่างหนึ่งล้มเหลว แต่การดำเนินการอีกอย่างหนึ่งสำเร็จ เราจะอยู่ในสถานะที่ไม่สอดคล้องกัน ซึ่งข้อมูลในส่วนหนึ่งของฐานข้อมูลไม่ตรงกับข้อมูลในอีกส่วนหนึ่ง

โชคดีที่ Cloud Firestore มีฟังก์ชันการทำธุรกรรมที่ช่วยให้เราอ่านและเขียนหลายรายการได้ในการดำเนินการแบบอะตอมเดียว ซึ่งช่วยให้มั่นใจได้ว่าข้อมูลจะยังคงสอดคล้องกัน

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. ค้นหาฟังก์ชัน FriendlyEats.prototype.addRating
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

ในบล็อกด้านบน เราจะทริกเกอร์ธุรกรรมเพื่ออัปเดตค่าตัวเลขของ avgRating และ numRatings ในเอกสารร้านอาหาร ในขณะเดียวกัน เราจะเพิ่ม rating ใหม่ลงในคอลเล็กชันย่อย ratings

12. รักษาข้อมูลของคุณให้ปลอดภัย

ที่จุดเริ่มต้นของโค้ดแล็บนี้ เราได้ตั้งกฎความปลอดภัยของแอปเพื่อจำกัดการเข้าถึงแอปพลิเคชัน

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data)
      && (key in request.resource.data)
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && unchanged("name");

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

กฎเหล่านี้จะจำกัดการเข้าถึงเพื่อให้มั่นใจว่าไคลเอ็นต์จะทำการเปลี่ยนแปลงที่ปลอดภัยเท่านั้น เช่น

  • การอัปเดตเอกสารร้านอาหารจะเปลี่ยนได้เฉพาะคะแนนเท่านั้น ไม่สามารถเปลี่ยนชื่อหรือข้อมูลอื่นๆ ที่เปลี่ยนแปลงไม่ได้
  • คุณจะสร้างการให้คะแนนได้ก็ต่อเมื่อรหัสผู้ใช้ตรงกับผู้ใช้ที่ลงชื่อเข้าใช้เท่านั้น ซึ่งจะช่วยป้องกันการปลอมแปลง

คุณใช้ Firebase CLI เพื่อทำให้กฎใช้งานได้ในโปรเจ็กต์ Firebase แทนการใช้คอนโซล Firebase ได้ ไฟล์ firestore.rules ในไดเรกทอรีการทำงานมีกฎจากด้านบนอยู่แล้ว หากต้องการนำกฎเหล่านี้ไปใช้งานจากระบบไฟล์ในเครื่อง (แทนการใช้ Firebase Console) ให้เรียกใช้คำสั่งต่อไปนี้

firebase deploy --only firestore:rules

13. บทสรุป

ในโค้ดแล็บนี้ คุณได้เรียนรู้วิธีอ่านและเขียนข้อมูลขั้นพื้นฐานและขั้นสูงด้วย Cloud Firestore รวมถึงวิธีรักษาความปลอดภัยในการเข้าถึงข้อมูลด้วยกฎความปลอดภัย ดูโซลูชันทั้งหมดได้ในที่เก็บ quickstarts-js

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Cloud Firestore ได้ที่แหล่งข้อมูลต่อไปนี้

14. [ไม่บังคับ] บังคับใช้ด้วย App Check

App Check ของ Firebase ช่วยปกป้องโดยการช่วยตรวจสอบและป้องกันการเข้าชมที่ไม่ต้องการในแอป ในขั้นตอนนี้ คุณจะรักษาความปลอดภัยในการเข้าถึงบริการโดยเพิ่ม App Check ด้วย reCAPTCHA Enterprise

ก่อนอื่น คุณจะต้องเปิดใช้ App Check และ reCaptcha

การเปิดใช้ reCAPTCHA Enterprise

  1. ใน Cloud Console ให้ค้นหาและเลือก reCAPTCHA Enterprise ในส่วนความปลอดภัย
  2. เปิดใช้บริการตามที่ได้รับแจ้ง แล้วคลิกสร้างคีย์
  3. ป้อนชื่อที่แสดงตามที่ได้รับแจ้ง แล้วเลือกเว็บไซต์เป็นประเภทแพลตฟอร์ม
  4. เพิ่ม URL ที่ติดตั้งใช้งานลงในรายการโดเมน และตรวจสอบว่าได้ยกเลิกการเลือกตัวเลือก "ใช้การท้าทายแบบช่องทําเครื่องหมาย" แล้ว
  5. คลิกสร้างคีย์ แล้วจัดเก็บคีย์ที่สร้างขึ้นไว้ในที่ปลอดภัย เนื่องจากคุณจะต้องใช้ในภายหลังในขั้นตอนนี้

การเปิดใช้ App Check

  1. ในคอนโซล Firebase ให้หาส่วนสร้างในแผงด้านซ้าย
  2. คลิกการตรวจสอบแอป แล้วคลิกปุ่มเริ่มต้นใช้งาน (หรือเปลี่ยนเส้นทางไปยัง คอนโซลโดยตรง)
  3. คลิกลงทะเบียน แล้วป้อนคีย์ reCAPTCHA Enterprise เมื่อมีข้อความแจ้ง จากนั้นคลิกบันทึก
  4. ในมุมมอง API ให้เลือก Storage แล้วคลิก Enforce ทำเช่นเดียวกันกับ Cloud Firestore

ตอนนี้ควรมีการบังคับใช้ App Check แล้ว รีเฟรชแอปแล้วลองสร้าง/ดูร้านอาหาร คุณควรได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

ซึ่งหมายความว่า App Check จะบล็อกคำขอที่ยังไม่ได้รับการตรวจสอบโดยค่าเริ่มต้น ตอนนี้มาเพิ่มการตรวจสอบความถูกต้องลงในแอปกัน

ไปที่ไฟล์ FriendlyEats.View.js แล้วอัปเดตฟังก์ชัน initAppCheck และเพิ่มคีย์ reCAPTCHA เพื่อเริ่มต้น App Check

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

ระบบจะเริ่มต้นอินสแตนซ์ appCheck ด้วย ReCaptchaEnterpriseProvider ที่มีคีย์ของคุณ และ isTokenAutoRefreshEnabled จะอนุญาตให้รีเฟรชโทเค็นโดยอัตโนมัติในแอป

หากต้องการเปิดใช้การทดสอบในเครื่อง ให้ค้นหาส่วนที่แอปเริ่มต้นในไฟล์ FriendlyEats.js แล้วเพิ่มบรรทัดต่อไปนี้ลงในฟังก์ชัน FriendlyEats.prototype.initAppCheck

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

ซึ่งจะบันทึกโทเค็นการแก้ไขข้อบกพร่องในคอนโซลของเว็บแอปในเครื่องของคุณในลักษณะคล้ายกับตัวอย่างต่อไปนี้

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

ตอนนี้ ให้ไปที่มุมมองแอปของ App Check ในคอนโซล Firebase

คลิกเมนูที่ซ่อนอยู่ แล้วเลือกจัดการโทเค็นการแก้ไขข้อบกพร่อง

จากนั้นคลิกเพิ่มโทเค็นการแก้ไขข้อบกพร่อง แล้ววางโทเค็นการแก้ไขข้อบกพร่องจากคอนโซลตามที่ได้รับแจ้ง

ยินดีด้วย ตอนนี้ App Check ควรใช้งานได้ในแอปแล้ว