ক্লাউড ফায়ারস্টোর আইওএস কোডল্যাব

1। সংক্ষিপ্ত বিবরণ

গোল

এই কোডল্যাবে আপনি Swift-এ iOS-এ ফায়ারস্টোর-সমর্থিত রেস্তোরাঁর সুপারিশ অ্যাপ তৈরি করবেন। আপনি শিখবেন কিভাবে:

  1. একটি iOS অ্যাপ থেকে Firestore-এ ডেটা পড়ুন এবং লিখুন
  2. রিয়েলটাইমে Firestore ডেটার পরিবর্তনগুলি শুনুন
  3. Firestore ডেটা সুরক্ষিত করতে Firebase প্রমাণীকরণ এবং নিরাপত্তা নিয়ম ব্যবহার করুন
  4. জটিল Firestore প্রশ্ন লিখুন

পূর্বশর্ত

এই কোডল্যাব শুরু করার আগে নিশ্চিত করুন যে আপনি ইনস্টল করেছেন:

  • Xcode সংস্করণ 14.0 (বা উচ্চতর)
  • CocoaPods 1.12.0 (বা উচ্চতর)

2. ফায়ারবেস কনসোল প্রকল্প তৈরি করুন

প্রকল্পে Firebase যোগ করুন

  1. ফায়ারবেস কনসোলে যান।
  2. নতুন প্রকল্প তৈরি করুন নির্বাচন করুন এবং আপনার প্রকল্পের নাম "ফায়ারস্টোর iOS কোডল্যাব"।

3. নমুনা প্রকল্প পান

কোডটি ডাউনলোড করুন

নমুনা প্রকল্প ক্লোন করে শুরু করুন এবং প্রকল্প ডিরেক্টরিতে pod update চালান:

git clone https://github.com/firebase/friendlyeats-ios
cd friendlyeats-ios
pod update

Xcode-এ FriendlyEats.xcworkspace খুলুন এবং এটি চালান (Cmd+R)। অ্যাপটি সঠিকভাবে কম্পাইল করা উচিত এবং লঞ্চের সাথে সাথেই ক্র্যাশ হওয়া উচিত, যেহেতু এটিতে একটি GoogleService-Info.plist ফাইল নেই। আমরা পরবর্তী ধাপে এটি সংশোধন করব।

Firebase সেট আপ করুন

একটি নতুন ফায়ারস্টোর প্রকল্প তৈরি করতে ডকুমেন্টেশন অনুসরণ করুন। একবার আপনি আপনার প্রকল্পটি পেয়ে গেলে, Firebase কনসোল থেকে আপনার প্রকল্পের GoogleService-Info.plist ফাইলটি ডাউনলোড করুন এবং এটিকে Xcode প্রকল্পের মূলে টেনে আনুন৷ অ্যাপটি সঠিকভাবে কনফিগার করেছে এবং লঞ্চের সময় আর ক্র্যাশ হচ্ছে না তা নিশ্চিত করতে প্রকল্পটি আবার চালান। লগ ইন করার পরে, আপনি নীচের উদাহরণের মত একটি ফাঁকা স্ক্রীন দেখতে পাবেন। আপনি লগ ইন করতে অক্ষম হলে, নিশ্চিত করুন যে আপনি প্রমাণীকরণের অধীনে Firebase কনসোলে ইমেল/পাসওয়ার্ড সাইন-ইন পদ্ধতি সক্রিয় করেছেন।

d5225270159c040b.png

4. ফায়ারস্টোরে ডেটা লিখুন

এই বিভাগে আমরা Firestore-এ কিছু ডেটা লিখব যাতে আমরা অ্যাপ UI পূরণ করতে পারি। এটি Firebase কনসোলের মাধ্যমে ম্যানুয়ালি করা যেতে পারে, কিন্তু আমরা একটি মৌলিক Firestore লেখা প্রদর্শন করতে অ্যাপেই এটি করব।

আমাদের অ্যাপের প্রধান মডেল অবজেক্ট হল একটি রেস্টুরেন্ট। ফায়ারস্টোর ডেটা নথি, সংগ্রহ এবং উপ-সংগ্রহগুলিতে বিভক্ত। আমরা প্রতিটি রেস্টুরেন্টকে restaurants নামক একটি শীর্ষ-স্তরের সংগ্রহে একটি নথি হিসাবে সংরক্ষণ করব। আপনি যদি Firestore ডেটা মডেল সম্পর্কে আরও জানতে চান, ডকুমেন্টেশনে নথি এবং সংগ্রহ সম্পর্কে পড়ুন।

Firestore-এ ডেটা যোগ করার আগে, আমাদের রেস্তোরাঁর সংগ্রহের একটি রেফারেন্স পেতে হবে। RestaurantsTableViewController.didTapPopulateButton(_:) পদ্ধতিতে লুপের ভিতরের জন্য নিম্নলিখিতটি যুক্ত করুন।

let collection = Firestore.firestore().collection("restaurants")

এখন যেহেতু আমাদের কাছে একটি সংগ্রহের রেফারেন্স রয়েছে আমরা কিছু ডেটা লিখতে পারি। আমরা যোগ করা কোডের শেষ লাইনের ঠিক পরে নিম্নলিখিত যোগ করুন:

let collection = Firestore.firestore().collection("restaurants")

// ====== ADD THIS ======
let restaurant = Restaurant(
  name: name,
  category: category,
  city: city,
  price: price,
  ratingCount: 0,
  averageRating: 0
)

collection.addDocument(data: restaurant.dictionary)

উপরের কোডটি রেস্টুরেন্ট সংগ্রহে একটি নতুন নথি যোগ করে। নথির ডেটা একটি অভিধান থেকে আসে, যা আমরা একটি রেস্তোরাঁর কাঠামো থেকে পাই।

আমরা প্রায় সেখানেই আছি– আমরা Firestore-এ নথি লিখতে পারার আগে আমাদের Firestore-এর নিরাপত্তা নিয়মগুলি খুলতে হবে এবং আমাদের ডাটাবেসের কোন অংশগুলি কোন ব্যবহারকারীদের দ্বারা লেখার যোগ্য হবে তা বর্ণনা করতে হবে। আপাতত, আমরা শুধুমাত্র প্রমাণীকৃত ব্যবহারকারীদের সমগ্র ডাটাবেসে পড়তে এবং লিখতে অনুমতি দেব। এটি একটি প্রোডাকশন অ্যাপের জন্য একটু বেশিই অনুমোদনযোগ্য, কিন্তু অ্যাপ-বিল্ডিং প্রক্রিয়া চলাকালীন আমরা যথেষ্ট শিথিল কিছু চাই তাই পরীক্ষা করার সময় আমরা ক্রমাগত প্রমাণীকরণের সমস্যায় পড়ব না। এই কোডল্যাবের শেষে আমরা কীভাবে আপনার নিরাপত্তা বিধিগুলিকে কঠোর করতে এবং অনিচ্ছাকৃত পঠন এবং লেখার সম্ভাবনা সীমিত করতে পারি সে সম্পর্কে কথা বলব৷

Firebase কনসোলের নিয়ম ট্যাবে নিম্নলিখিত নিয়মগুলি যোগ করুন এবং তারপরে প্রকাশ করুন ক্লিক করুন।

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

আমরা পরে নিরাপত্তা বিধি নিয়ে বিস্তারিত আলোচনা করব, কিন্তু আপনি যদি তাড়াহুড়ো করে থাকেন, তাহলে নিরাপত্তা নিয়মের ডকুমেন্টেশন দেখে নিন।

অ্যাপটি চালান এবং সাইন ইন করুন। তারপরে উপরের বাম দিকে " পপুলেট " বোতামটি আলতো চাপুন, যা রেস্তোরাঁর নথির একটি ব্যাচ তৈরি করবে, যদিও আপনি এটি অ্যাপটিতে এখনও দেখতে পাবেন না।

এরপর, Firebase কনসোলে Firestore ডেটা ট্যাবে নেভিগেট করুন। আপনি এখন রেস্টুরেন্ট সংগ্রহে নতুন এন্ট্রি দেখতে হবে:

স্ক্রীন শট 2017-07-06 12.45.38 PM.png

অভিনন্দন, আপনি এইমাত্র একটি iOS অ্যাপ থেকে Firestore-এ ডেটা লিখেছেন! পরবর্তী বিভাগে আপনি শিখবেন কীভাবে ফায়ারস্টোর থেকে ডেটা পুনরুদ্ধার করতে হয় এবং এটি অ্যাপে প্রদর্শন করতে হয়।

5. ফায়ারস্টোর থেকে ডেটা প্রদর্শন করুন

এই বিভাগে আপনি শিখবেন কিভাবে ফায়ারস্টোর থেকে ডেটা পুনরুদ্ধার করতে হয় এবং অ্যাপে এটি প্রদর্শন করতে হয়। দুটি মূল ধাপ হল একটি ক্যোয়ারী তৈরি করা এবং একটি স্ন্যাপশট লিসেনার যোগ করা। এই শ্রোতাকে সমস্ত বিদ্যমান ডেটা সম্পর্কে অবহিত করা হবে যা প্রশ্নের সাথে মেলে এবং রিয়েল টাইমে আপডেটগুলি পাবে।

প্রথমে, চলুন এমন একটি ক্যোয়ারী তৈরি করি যা রেস্তোরাঁর ডিফল্ট, আনফিল্টার করা তালিকা পরিবেশন করবে। RestaurantsTableViewController.baseQuery() এর বাস্তবায়নের দিকে নজর দিন :

return Firestore.firestore().collection("restaurants").limit(to: 50)

এই প্রশ্নটি "রেস্তোরাঁ" নামে শীর্ষ-স্তরের সংগ্রহের 50টি পর্যন্ত রেস্তোরাঁ পুনরুদ্ধার করে৷ এখন যেহেতু আমাদের কাছে একটি প্রশ্ন আছে, আমাদের অ্যাপে Firestore থেকে ডেটা লোড করার জন্য আমাদের একটি স্ন্যাপশট লিসেনার সংযুক্ত করতে হবে। stopObserving() এ কল করার ঠিক পরে RestaurantsTableViewController.observeQuery() পদ্ধতিতে নিম্নলিখিত কোডটি যোগ করুন।

listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
  guard let snapshot = snapshot else {
    print("Error fetching snapshot results: \(error!)")
    return
  }
  let models = snapshot.documents.map { (document) -> Restaurant in
    if let model = Restaurant(dictionary: document.data()) {
      return model
    } else {
      // Don't use fatalError here in a real app.
      fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
    }
  }
  self.restaurants = models
  self.documents = snapshot.documents

  if self.documents.count > 0 {
    self.tableView.backgroundView = nil
  } else {
    self.tableView.backgroundView = self.backgroundView
  }

  self.tableView.reloadData()
}

উপরের কোডটি Firestore থেকে সংগ্রহ ডাউনলোড করে এবং স্থানীয়ভাবে একটি অ্যারেতে সংরক্ষণ করে। addSnapshotListener(_:) কলটি একটি স্ন্যাপশট লিসেনারকে কোয়েরিতে যোগ করে যা সার্ভারে ডেটা পরিবর্তনের সময় ভিউ কন্ট্রোলার আপডেট করবে। আমরা স্বয়ংক্রিয়ভাবে আপডেট পাই এবং ম্যানুয়ালি পরিবর্তন করতে হবে না। মনে রাখবেন, সার্ভার-সাইড পরিবর্তনের ফলে এই স্ন্যাপশট শ্রোতাকে যে কোনো সময় আহ্বান করা যেতে পারে তাই এটি গুরুত্বপূর্ণ যে আমাদের অ্যাপ পরিবর্তনগুলি পরিচালনা করতে পারে।

আমাদের অভিধানগুলিকে স্ট্রাকটে ম্যাপ করার পরে ( Restaurant.swift দেখুন), ডেটা প্রদর্শন করা শুধুমাত্র কয়েকটি দর্শন বৈশিষ্ট্য নির্ধারণের বিষয়। RestaurantTableViewCell.populate(restaurant:) -এ নিম্নলিখিত লাইনগুলি যুক্ত করুন RestaurantsTableViewController.swift

nameLabel.text = restaurant.name
cityLabel.text = restaurant.city
categoryLabel.text = restaurant.category
starsView.rating = Int(restaurant.averageRating.rounded())
priceLabel.text = priceString(from: restaurant.price)

এই পপুলেট পদ্ধতিটিকে টেবিল ভিউ ডেটা সোর্সের tableView(_:cellForRowAtIndexPath:) পদ্ধতি থেকে বলা হয়, যা আগে থেকে পৃথক টেবিল ভিউ কক্ষে মান প্রকারের সংগ্রহ ম্যাপ করার যত্ন নেয়।

অ্যাপটি আবার চালান এবং যাচাই করুন যে আমরা আগে কনসোলে যে রেস্তোরাঁগুলি দেখেছিলাম সেগুলি এখন সিমুলেটর বা ডিভাইসে দৃশ্যমান৷ আপনি যদি এই বিভাগটি সফলভাবে সম্পন্ন করেন তবে আপনার অ্যাপ এখন ক্লাউড ফায়ারস্টোরের সাথে ডেটা পড়ছে এবং লিখছে!

391c0259bf05ac25.png

6. বাছাই এবং ফিল্টারিং ডেটা

বর্তমানে আমাদের অ্যাপ রেস্তোরাঁর একটি তালিকা প্রদর্শন করে, কিন্তু ব্যবহারকারীর প্রয়োজনের ভিত্তিতে ফিল্টার করার কোনো উপায় নেই। এই বিভাগে আপনি ফিল্টারিং সক্ষম করতে Firestore এর উন্নত অনুসন্ধান ব্যবহার করবেন।

এখানে সমস্ত ডিম সাম রেস্তোরাঁগুলি আনার জন্য একটি সাধারণ প্রশ্নের উদাহরণ দেওয়া হল:

let filteredQuery = query.whereField("category", isEqualTo: "Dim Sum")

এর নাম থেকে বোঝা যায়, whereField(_:isEqualTo:) পদ্ধতি আমাদের ক্যোয়ারী শুধুমাত্র সংগ্রহের সদস্যদের ডাউনলোড করবে যাদের ক্ষেত্রগুলি আমাদের সেট করা বিধিনিষেধ পূরণ করে। এই ক্ষেত্রে, এটি শুধুমাত্র সেই রেস্তোরাঁগুলিকে ডাউনলোড করবে যেখানে category "Dim Sum"

এই অ্যাপটিতে ব্যবহারকারী নির্দিষ্ট প্রশ্ন তৈরি করতে একাধিক ফিল্টার চেইন করতে পারেন, যেমন "সান ফ্রান্সিসকোতে পিজা" বা "লস এঞ্জেলেসের সামুদ্রিক খাবার জনপ্রিয়তা দ্বারা আদেশ করা"।

RestaurantsTableViewController.swift খুলুন এবং query(withCategory:city:price:sortBy:) :

if let category = category, !category.isEmpty {
  filtered = filtered.whereField("category", isEqualTo: category)
}

if let city = city, !city.isEmpty {
  filtered = filtered.whereField("city", isEqualTo: city)
}

if let price = price {
  filtered = filtered.whereField("price", isEqualTo: price)
}

if let sortBy = sortBy, !sortBy.isEmpty {
  filtered = filtered.order(by: sortBy)
}

উপরের স্নিপেটটি ব্যবহারকারীর ইনপুটের উপর ভিত্তি করে একটি একক যৌগিক প্রশ্ন তৈরি করতে একাধিক whereField এবং order ক্লজ যোগ করে। এখন আমাদের ক্যোয়ারী শুধুমাত্র ব্যবহারকারীর প্রয়োজনীয়তার সাথে মেলে এমন রেস্তোরাঁগুলিকে ফিরিয়ে দেবে৷

আপনার প্রকল্পটি চালান এবং যাচাই করুন যে আপনি মূল্য, শহর এবং বিভাগ দ্বারা ফিল্টার করতে পারেন (বিষয়টি এবং শহরের নাম ঠিক টাইপ করতে ভুলবেন না)। পরীক্ষা করার সময় আপনি আপনার লগগুলিতে ত্রুটিগুলি দেখতে পাবেন যা এইরকম দেখাচ্ছে:

Error fetching snapshot results: Error Domain=io.grpc Code=9 
"The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=..." 
UserInfo={NSLocalizedDescription=The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...}

এর কারণ হল Firestore-এর বেশিরভাগ যৌগিক প্রশ্নের জন্য সূচী প্রয়োজন। ক্যোয়ারীতে ইনডেক্সের প্রয়োজন ফায়ারস্টোরকে স্কেলে দ্রুত রাখে। ত্রুটি বার্তা থেকে লিঙ্কটি খুললে স্বয়ংক্রিয়ভাবে Firebase কনসোলে সূচী তৈরির UI খুলবে সঠিক পরামিতি পূরণ করে। Firestore-এ সূচী সম্পর্কে আরও জানতে, ডকুমেন্টেশন দেখুন

7. একটি লেনদেনে ডেটা লেখা

এই বিভাগে, আমরা ব্যবহারকারীদের রেস্তোঁরাগুলিতে পর্যালোচনা জমা দেওয়ার ক্ষমতা যুক্ত করব। এই পর্যন্ত, আমাদের সমস্ত লেখাই পারমাণবিক এবং তুলনামূলকভাবে সহজ। যদি তাদের মধ্যে কোনো ত্রুটি হয়, আমরা সম্ভবত ব্যবহারকারীকে সেগুলি পুনরায় চেষ্টা করতে বা স্বয়ংক্রিয়ভাবে পুনরায় চেষ্টা করার জন্য অনুরোধ করব৷

একটি রেস্টুরেন্টে একটি রেটিং যোগ করার জন্য আমাদের একাধিক পঠন এবং লেখার সমন্বয় করতে হবে। প্রথমে পর্যালোচনাটি নিজেই জমা দিতে হবে এবং তারপরে রেস্টুরেন্টের রেটিং গণনা এবং গড় রেটিং আপডেট করতে হবে। যদি এইগুলির মধ্যে একটি ব্যর্থ হয় তবে অন্যটি না হয়, তাহলে আমরা একটি অসামঞ্জস্যপূর্ণ অবস্থায় থাকি যেখানে আমাদের ডাটাবেসের একটি অংশের ডেটা অন্যটির ডেটার সাথে মেলে না।

সৌভাগ্যবশত, ফায়ারস্টোর লেনদেনের কার্যকারিতা প্রদান করে যা আমাদেরকে একটি একক পারমাণবিক অপারেশনে একাধিক রিড এবং রাইট করতে দেয়, আমাদের ডেটা সামঞ্জস্যপূর্ণ থাকে তা নিশ্চিত করে।

RestaurantDetailViewController.reviewController(_:didSubmitFormWithReview:) এ সমস্ত লেট ঘোষণার নীচে নিম্নলিখিত কোডটি যোগ করুন।

let firestore = Firestore.firestore()
firestore.runTransaction({ (transaction, errorPointer) -> Any? in

  // Read data from Firestore inside the transaction, so we don't accidentally
  // update using stale client data. Error if we're unable to read here.
  let restaurantSnapshot: DocumentSnapshot
  do {
    try restaurantSnapshot = transaction.getDocument(reference)
  } catch let error as NSError {
    errorPointer?.pointee = error
    return nil
  }

  // Error if the restaurant data in Firestore has somehow changed or is malformed.
  guard let data = restaurantSnapshot.data(),
        let restaurant = Restaurant(dictionary: data) else {

    let error = NSError(domain: "FireEatsErrorDomain", code: 0, userInfo: [
      NSLocalizedDescriptionKey: "Unable to write to restaurant at Firestore path: \(reference.path)"
    ])
    errorPointer?.pointee = error
    return nil
  }

  // Update the restaurant's rating and rating count and post the new review at the 
  // same time.
  let newAverage = (Float(restaurant.ratingCount) * restaurant.averageRating + Float(review.rating))
      / Float(restaurant.ratingCount + 1)

  transaction.setData(review.dictionary, forDocument: newReviewReference)
  transaction.updateData([
    "numRatings": restaurant.ratingCount + 1,
    "avgRating": newAverage
  ], forDocument: reference)
  return nil
}) { (object, error) in
  if let error = error {
    print(error)
  } else {
    // Pop the review controller on success
    if self.navigationController?.topViewController?.isKind(of: NewReviewViewController.self) ?? false {
      self.navigationController?.popViewController(animated: true)
    }
  }
}

আপডেট ব্লকের ভিতরে, লেনদেন বস্তু ব্যবহার করে আমরা যে সমস্ত ক্রিয়াকলাপ করি তা Firestore দ্বারা একক পারমাণবিক আপডেট হিসাবে গণ্য হবে। সার্ভারে আপডেট ব্যর্থ হলে, Firestore স্বয়ংক্রিয়ভাবে এটি কয়েকবার পুনরায় চেষ্টা করবে। এর মানে হল যে আমাদের ত্রুটির অবস্থাটি সম্ভবত একটি একক ত্রুটি বারবার ঘটতে পারে, উদাহরণস্বরূপ যদি ডিভাইসটি সম্পূর্ণ অফলাইনে থাকে বা ব্যবহারকারী যে পথে লেখার চেষ্টা করছেন তাতে লেখার জন্য অনুমোদিত না হয়৷

8. নিরাপত্তা নিয়ম

আমাদের অ্যাপের ব্যবহারকারীরা আমাদের ডাটাবেসের প্রতিটি ডেটা পড়তে এবং লিখতে সক্ষম হবেন না। উদাহরণস্বরূপ, প্রত্যেকেরই একটি রেস্তোরাঁর রেটিং দেখতে সক্ষম হওয়া উচিত, তবে শুধুমাত্র একজন প্রমাণীকৃত ব্যবহারকারীকে একটি রেটিং পোস্ট করার অনুমতি দেওয়া উচিত। ক্লায়েন্টে ভাল কোড লেখার জন্য এটি যথেষ্ট নয়, সম্পূর্ণ সুরক্ষিত হওয়ার জন্য ব্যাকএন্ডে আমাদের ডেটা সুরক্ষা মডেলটি নির্দিষ্ট করতে হবে। এই বিভাগে আমরা শিখব কিভাবে আমাদের ডেটা সুরক্ষিত রাখতে Firebase নিরাপত্তা নিয়মগুলি ব্যবহার করতে হয়।

প্রথমত, কোডল্যাবের শুরুতে আমরা যে নিরাপত্তা বিধিগুলি লিখেছিলাম তার গভীরে নজর দেওয়া যাক৷ Firebase কনসোল খুলুন এবং Firestore ট্যাবে Database > Rules- এ নেভিগেট করুন।

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      // Only authenticated users can read or write data
      allow read, write: if request.auth != null;
    }
  }
}

উপরের নিয়মে request ভেরিয়েবল হল একটি গ্লোবাল ভেরিয়েবল সমস্ত নিয়মে উপলব্ধ, এবং আমরা যে শর্তসাপেক্ষে যোগ করেছি তা নিশ্চিত করে যে ব্যবহারকারীদের কিছু করার অনুমতি দেওয়ার আগে অনুরোধটি প্রমাণীকৃত হয়েছে। এটি অননুমোদিত ব্যবহারকারীদের আপনার ডেটাতে অননুমোদিত পরিবর্তন করতে Firestore API ব্যবহার করতে বাধা দেয়। এটি একটি ভাল সূচনা, তবে আমরা আরও শক্তিশালী জিনিসগুলি করতে Firestore নিয়মগুলি ব্যবহার করতে পারি৷

আসুন পর্যালোচনা লেখাগুলিকে সীমাবদ্ধ করি যাতে পর্যালোচনার ব্যবহারকারী আইডি অবশ্যই প্রমাণীকৃত ব্যবহারকারীর আইডির সাথে মেলে। এটি নিশ্চিত করে যে ব্যবহারকারীরা একে অপরের ছদ্মবেশ ধারণ করতে পারবে না এবং প্রতারণামূলক পর্যালোচনাগুলি ছেড়ে যাবে না। নিম্নলিখিত দিয়ে আপনার নিরাপত্তা নিয়ম প্রতিস্থাপন করুন:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurants/{any}/ratings/{rating} {
      // Users can only write ratings with their user ID
      allow read;
      allow write: if request.auth != null 
                   && request.auth.uid == request.resource.data.userId;
    }
  
    match /restaurants/{any} {
      // Only authenticated users can read or write data
      allow read, write: if request.auth != null;
    }
  }
}

প্রথম ম্যাচের বিবৃতি restaurants সংগ্রহের সাথে সম্পর্কিত যেকোন নথির উপ-সংকলন নামের ratings সাথে মেলে। শর্তসাপেক্ষে allow write পর্যালোচনার ব্যবহারকারী আইডি ব্যবহারকারীর সাথে মেলে না তাহলে কোনো পর্যালোচনা জমা হওয়া থেকে বাধা দেয়। দ্বিতীয় ম্যাচ স্টেটমেন্ট যেকোনো প্রমাণীকৃত ব্যবহারকারীকে ডাটাবেসে রেস্টুরেন্ট পড়তে এবং লিখতে দেয়।

এটি আমাদের পর্যালোচনাগুলির জন্য সত্যিই ভাল কাজ করে, কারণ আমরা আগে আমাদের অ্যাপে যে অন্তর্নিহিত গ্যারান্টি লিখেছিলাম তা স্পষ্টভাবে বলার জন্য নিরাপত্তা নিয়মগুলি ব্যবহার করেছি–যা ব্যবহারকারীরা শুধুমাত্র তাদের নিজস্ব পর্যালোচনা লিখতে পারে৷ যদি আমরা পর্যালোচনার জন্য একটি সম্পাদনা বা মুছে ফেলার ফাংশন যোগ করি, তবে এই সঠিক নিয়মগুলির সেটটি ব্যবহারকারীদের অন্য ব্যবহারকারীদের পর্যালোচনাগুলিকে সংশোধন বা মুছে ফেলা থেকেও বাধা দেবে। কিন্তু Firestore নিয়মগুলি আরও দানাদার ফ্যাশনে ব্যবহার করা যেতে পারে যাতে সম্পূর্ণ নথির পরিবর্তে নথিগুলির মধ্যে পৃথক ক্ষেত্রগুলিতে লেখা সীমাবদ্ধ করা যায়। আমরা এটি ব্যবহার করে ব্যবহারকারীদের শুধুমাত্র রেটিং, গড় রেটিং, এবং রেটিং সংখ্যা আপডেট করার অনুমতি দিতে পারি, একটি দূষিত ব্যবহারকারী একটি রেস্টুরেন্টের নাম বা অবস্থান পরিবর্তন করার সম্ভাবনা দূর করে।

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurants/{restaurant} {
      match /ratings/{rating} {
        allow read: if request.auth != null;
        allow write: if request.auth != null 
                     && request.auth.uid == request.resource.data.userId;
      }
    
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && request.resource.data.name == resource.data.name
                    && request.resource.data.city == resource.data.city
                    && request.resource.data.price == resource.data.price
                    && request.resource.data.category == resource.data.category;
    }
  }
}

এখানে আমরা আমাদের লেখার অনুমতি তৈরি এবং আপডেটে বিভক্ত করেছি যাতে আমরা কোন ক্রিয়াকলাপগুলির অনুমতি দেওয়া উচিত সে সম্পর্কে আরও নির্দিষ্ট হতে পারি। কোডল্যাবের শুরুতে আমরা যে পপুলেট বোতামটি তৈরি করেছি তার কার্যকারিতা সংরক্ষণ করে যেকোন ব্যবহারকারী ডাটাবেসে রেস্টুরেন্ট লিখতে পারে, কিন্তু একবার রেস্তোরাঁর নাম, অবস্থান, মূল্য এবং বিভাগ পরিবর্তন করা যাবে না। আরও নির্দিষ্টভাবে, শেষ নিয়মে ডাটাবেসে ইতিমধ্যে বিদ্যমান ক্ষেত্রগুলির একই নাম, শহর, মূল্য এবং বিভাগ বজায় রাখার জন্য যে কোনও রেস্তোরাঁর আপডেট অপারেশন প্রয়োজন।

নিরাপত্তা বিধিগুলির সাথে আপনি কী করতে পারেন সে সম্পর্কে আরও জানতে, ডকুমেন্টেশনটি দেখুন৷

9. উপসংহার

এই কোডল্যাবে, আপনি শিখেছেন কিভাবে ফায়ারস্টোরের সাথে প্রাথমিক এবং উন্নত পঠন এবং লিখতে হয়, সেইসাথে কীভাবে সুরক্ষা নিয়মের সাথে ডেটা অ্যাক্সেস সুরক্ষিত করা যায়। আপনি codelab-complete শাখায় সম্পূর্ণ সমাধান পেতে পারেন।

ফায়ারস্টোর সম্পর্কে আরও জানতে, নিম্নলিখিত সংস্থানগুলি দেখুন: