Realtime Database ট্রিগার

Cloud Functions-এর সাহায্যে, আপনি ক্লায়েন্ট কোড আপডেট করার প্রয়োজন ছাড়াই Firebase Realtime Database-এ ইভেন্ট ম্যানেজ করতে পারবেন। Cloud Functions আপনাকে সম্পূর্ণ অ্যাডমিনিস্ট্রেটিভ প্রিভিলেজ সহ Realtime Database অপারেশন রান করতে দেয় এবং Realtime Database-এ করা প্রতিটি পরিবর্তন আলাদা আলাদাভাবে প্রসেস করা হয়। আপনি Firebase Realtime Database ডেটা স্ন্যাপশট অথবা অ্যাডমিন SDK-এর মাধ্যমে পরিবর্তন করতে পারবেন।

সাধারণ লাইফসাইকেলে, Firebase Realtime Database ফাংশন নিম্নলিখিত কাজগুলি করে:

  1. নির্দিষ্ট Realtime Database পাথে পরিবর্তনের জন্য অপেক্ষা করে।
  2. কোনও ইভেন্ট ঘটলে ট্রিগার হয় এবং নিজের কাজ করে।
  3. সেই পাথে সেভ করা ডেটার স্ন্যাপশট থাকা ডেটা অবজেক্ট পায়।

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

ফাংশনের লোকেশন সেট করা

Realtime Database ইনস্ট্যান্সের লোকেশন ও ফাংশনের লোকেশনের মধ্যে দূরত্ব থাকলে নেটওয়ার্ক লেটেন্সি অনেক বেশি হতে পারে। এছাড়াও, অঞ্চলগুলির মধ্যে ম্যাচ না করলে, ডিপ্লয়মেন্ট ব্যর্থ হতে পারে। এইসব পরিস্থিতি এড়াতে, ফাংশন লোকেশন উল্লেখ করুন যাতে এটি ডেটাবেস ইনস্ট্যান্স লোকেশনের সাথে ম্যাচ করে।

Realtime Database ইভেন্ট ম্যানেজ করা

ফাংশন আপনাকে দুটি লেভেলে নির্দিষ্ট করে Realtime Database ইভেন্ট ম্যানেজ করতে দেয়; আপনি শুধুমাত্র লেখা, তৈরি করা, আপডেট করা, বা মুছে দেওয়া ইভেন্ট শুনতে পারেন অথবা রেফারেন্সে যেকোনও ধরনের পরিবর্তন শুনতে পারেন।

Realtime Database ইভেন্টের উত্তর দেওয়ার জন্য এইসব হ্যান্ডলার উপলভ্য:

Node.js

  • onValueWritten() Realtime Database-এ ডেটা তৈরি, আপডেট বা মুছে দেওয়া হলে ট্রিগার করা হয়।
  • onValueCreated() শুধুমাত্র Realtime Database-এ ডেটা তৈরি হলে ট্রিগার করা হয়।
  • onValueUpdated() শুধুমাত্র Realtime Database-এ ডেটা আপডেট করা হলে ট্রিগার করা হয়।
  • onValueDeleted() শুধুমাত্র Realtime Database-এ ডেটা মুছে ফেলা হলে ট্রিগার করা হয়।

Python

  • on_value_written() Realtime Database-এ ডেটা তৈরি, আপডেট বা মুছে দেওয়া হলে ট্রিগার করা হয়।
  • on_value_created() শুধুমাত্র Realtime Database-এ ডেটা তৈরি হলে ট্রিগার করা হয়।
  • on_value_updated() শুধুমাত্র Realtime Database-এ ডেটা আপডেট করা হলে ট্রিগার করা হয়।
  • on_value_deleted() শুধুমাত্র Realtime Database-এ ডেটা মুছে ফেলা হলে ট্রিগার করা হয়।

প্রয়োজনীয় মডিউল ইমপোর্ট করা

আপনার ফাংশন সোর্সে, আপনি যেসব SDK মডিউল ব্যবহার করতে চান সেগুলি অবশ্যই ইমপোর্ট করতে হবে। এই স্যাম্পেলের জন্য, Realtime Database-এ লেখার জন্য Firebase Admin SDK মডিউলের সাথে HTTP এবং Realtime Database মডিউল ইমপোর্ট করা প্রয়োজন।

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/https");
const {onValueCreated} = require("firebase-functions/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

Python

# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

ইনস্ট্যান্স ও পাথ উল্লেখ করা

আপনার ফাংশন কখন ও কোথায় ট্রিগার করা উচিত তা নিয়ন্ত্রণ করতে, পাথ ও ঐচ্ছিকভাবে Realtime Database ইনস্ট্যান্সের মাধ্যমে আপনার ফাংশন কনফিগার করুন। আপনি কোনও ইনস্ট্যান্স নির্দিষ্ট না করলে, ফাংশনটি ফাংশন অঞ্চলের মধ্যে থাকা সব Realtime Database ইনস্ট্যান্সের কথা শুনবে। এছাড়াও, একই অঞ্চলের দৃষ্টান্তের বাছাই করা সাবসেটে ডেপ্লয় করার জন্য আপনি Realtime Database দৃষ্টান্ত প্যাটার্ন নির্দিষ্ট করতে পারেন।

যেমন:

Node.js

// All Realtime Database instances at path "/user/{uid}"
// There must be at least one Realtime Database present.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
  },
  (event) => {
    // …
  }
);

Python

# All Realtime Database instances at path "/user/{uid}"
# There must be at least one Realtime Database present.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

এইসব প্যারামিটার আপনার ফাংশনকে Realtime Database ইনস্ট্যান্সের মধ্যে নির্দিষ্ট পাথে রাইট অপারেশন হ্যান্ডেল করার নির্দেশ দেয়।

পাথ স্পেসিফিকেশন সব রাইটের সাথে ম্যাচ করে যা কোনও পাথকে স্পর্শ করে, এর মধ্যে সেইসব রাইটও অন্তর্ভুক্ত যা এর নিচে যেকোনও জায়গায় ঘটে। আপনি যদি নিজের ফাংশনের জন্য পাথ /foo/bar হিসেবে সেট করেন, তাহলে এটি এই দুটি লোকেশনেই ইভেন্টের সাথে ম্যাচ করে:

 /foo/bar
 /foo/bar/baz/really/deep/path

দুটি ক্ষেত্রেই, Firebase ধরে নেয় যে ইভেন্টটি /foo/bar-এ হয়েছে এবং ইভেন্ট ডেটাতে /foo/bar-এ পুরনো ও নতুন ডেটা অন্তর্ভুক্ত থাকে। ইভেন্ট ডেটা বড় হতে পারে, তাই আপনার ডেটাবেসের রুট ডিরেক্টরির কাছাকাছি একটি ফাংশনের পরিবর্তে আরও গভীর পাথে একাধিক ফাংশন ব্যবহার করার কথা বিবেচনা করুন। সবচেয়ে ভালো পারফর্ম্যান্সের জন্য, সবচেয়ে গভীর লেভেলে সম্ভব হলে শুধুমাত্র সেই ডেটা অনুরোধ করুন।

ওয়াইল্ডকার্ড ও ক্যাপচার

ক্যাপচার করার জন্য আপনি {key}, {key=*}, {key=prefix*}, {key=*suffix} ব্যবহার করতে পারবেন। সিঙ্গেল-সেগমেন্ট ওয়াইল্ডকার্ডিংয়ের জন্য *, prefix*, *suffix। মনে রাখবেন: ** একাধিক সেগমেন্টের ওয়াইল্ডকার্ডিংকে বোঝায়, যা Realtime Database-এ কাজ করে না। পাথ প্যাটার্ন বোঝা দেখুন।

পাথ ওয়াইল্ডকার্ডিং। আপনি ওয়াইল্ডকার্ড হিসেবে পাথ কম্পোনেন্ট নির্দিষ্ট করতে পারেন:

  • তারকাচিহ্ন ব্যবহার করে, *। যেমন, foo/*, foo/-এর নিচে নোড হায়ারার্কির যেকোনও একটি লেভেলের চাইল্ডের সাথে ম্যাচ করে।
  • ঠিক অ্যাসটেরিক্স, * আছে এমন সেগমেন্ট ব্যবহার করা। যেমন, foo/app*-us foo/-এর নিচে app প্রিফিক্স ও -us সাফিক্স সহ যেকোনও চাইল্ড সেগমেন্টের সাথে ম্যাচ করে।

ওয়াইল্ডকার্ড সহ পাথ, যেমন, একটি রাইট থেকে একাধিক ইভেন্ট ম্যাচ করতে পারে। এর একটি ইনসার্ট

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

"/foo/*" পাথের সাথে দু'বার ম্যাচ করেছে: একবার "hello": "world"-এর সাথে এবং আবার "firebase": "functions"-এর সাথে।

পাথ ক্যাপচার করা। আপনার ফাংশন কোডে (যেমন, /user/{uid}, /user/{uid=*-us}) ব্যবহার করার জন্য নামযুক্ত ভেরিয়েবলে পাথ ম্যাচ ক্যাপচার করতে পারবেন।

ক্যাপচার ভেরিয়েবলের ভ্যালু আপনার ফাংশনের database.DatabaseEvent.params অবজেক্টের মধ্যে উপলভ্য।

ইনস্ট্যান্স ওয়াইল্ডকার্ডিং। এছাড়াও, আপনি ওয়াইল্ডকার্ডিং ব্যবহার করে কোনও ইনস্ট্যান্স কম্পোনেন্ট নির্দিষ্ট করতে পারবেন। ইনস্ট্যান্স ওয়াইল্ডকার্ডে প্রিফিক্স, সাফিক্স বা দু'টিই থাকতে পারে (যেমন, my-app-*-prod)।

ওয়াইল্ডকার্ড ও ক্যাপচার রেফারেন্স

Cloud Functions (২য় জেনারেশন) ও Realtime Database-এর ক্ষেত্রে, ref ও instance নির্দিষ্ট করার সময় প্যাটার্ন ব্যবহার করা যেতে পারে। প্রতিটি ট্রিগার ইন্টারফেসে ফাংশনের স্কোপিংয়ের জন্য নিম্নলিখিত বিকল্প থাকবে:

নির্দিষ্ট করা ref নির্দিষ্ট করা instance আচরণ
সিঙ্গেল (/foo/bar) নির্দিষ্ট করে উল্লেখ না করা ফাংশন অঞ্চলের সব ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
সিঙ্গেল (/foo/bar) সিঙ্গেল (‘my-new-db') ফাংশন অঞ্চলে নির্দিষ্ট ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
সিঙ্গেল (/foo/bar) প্যাটার্ন (‘inst-prefix*') ফাংশন অঞ্চলের প্যাটার্নের সাথে ম্যাচ করে এমন সব ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
প্যাটার্ন (/foo/{bar}) নির্দিষ্ট করে উল্লেখ না করা ফাংশন অঞ্চলের সব ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
প্যাটার্ন (/foo/{bar}) সিঙ্গেল (‘my-new-db') ফাংশন অঞ্চলে নির্দিষ্ট ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
প্যাটার্ন (/foo/{bar}) প্যাটার্ন (‘inst-prefix*') ফাংশন অঞ্চলের প্যাটার্নের সাথে ম্যাচ করে এমন সব ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।

ইভেন্ট ডেটা ম্যানেজ করা

Realtime Database ইভেন্ট ট্রিগার হলে, এটি আপনার হ্যান্ডলার ফাংশনে একটি Event অবজেক্ট পাস করে। এই অবজেক্টে একটি data প্রপার্টি আছে, যা তৈরি করা ও মুছে দেওয়া ইভেন্টের ক্ষেত্রে, তৈরি করা বা মুছে দেওয়া ডেটার একটি স্ন্যাপশট থাকে।

এই উদাহরণে, ফাংশনটি রেফার করা পাথের ডেটা রিট্রিভ করে, সেই লোকেশনে থাকা স্ট্রিংটিকে বড় হাতের অক্ষরে কনভার্ট করে এবং সেই পরিবর্তিত স্ট্রিংটি ডেটাবেসে লেখে:

Node.js

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);

Python

@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

আগের ভ্যালু পড়ুন

write বা update ইভেন্টের জন্য, data প্রপার্টি হল একটি Change অবজেক্ট যাতে দুটি স্ন্যাপশট থাকে যা ট্রিগার করা ইভেন্টের আগে ও পরের ডেটা স্টেটকে দেখায়। Change অবজেক্টে একটি before প্রপার্টি আছে যা আপনাকে ইভেন্টের Realtime Database আগে সেভ করা ডেটা চেক করতে দেয় এবং একটি after প্রপার্টি আছে যা ইভেন্ট হওয়ার পরে ডেটার স্ট্যাটাস দেখায়।

যেমন, before প্রপার্টি ব্যবহার করে এটি নিশ্চিত করা যেতে পারে যে ফাংশনটি টেক্সট প্রথম তৈরি করার সময় শুধুমাত্র বড় হাতের অক্ষর ব্যবহার করবে:

Node.js

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        // You must return a Promise when performing asynchronous tasks inside a Functions such as
        // writing to the Firebase Realtime Database.
        // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
        return event.data.after.ref.parent.child('uppercase').set(uppercase);
      });

Python

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

যাচাইকরণ প্রসঙ্গ অ্যাক্সেস করা

RTDB ইভেন্টআর্ক ইভেন্ট দ্বারা ট্রিগার করা ফাংশনের জন্য, যাচাইকরণ প্রসঙ্গটি ইভেন্ট পেলোডের মধ্যে অন্তর্ভুক্ত থাকে:

  • authtype: ইভেন্ট ট্রিগার করা প্রিন্সিপালের ধরন। সম্ভাব্য ভ্যালুগুলি হল:
    • app_user: ডেভেলপারের অ্যাপ্লিকেশনের একজন গ্রাহক।
    • admin: একটি পরিষেবা অ্যাকাউন্ট।
    • unauthenticated: যাচাই না করা ব্যবহারকারী।
    • unknown: যাচাইকরণ সংক্রান্ত তথ্য উপলভ্য না থাকলে ডিফল্ট হিসেবে সেট করা হয়।
  • authid: প্রিন্সিপালের অনন্য শনাক্তকারী।
    • authtype যদি app_user হয়, তাহলে এটি হল ব্যবহারকারীর UID.
    • authtype admin হলে, এটি হল পরিষেবা অ্যাকাউন্ট বা IAM ব্যবহারকারীর ইমেল।

যে ব্যবহারকারী ফাংশন ট্রিগার করেছেন তিনি অ্যাডমিনিস্ট্রেটর না হলে, এই কোডটি শুধুমাত্র মেসেজের টেক্সটকে বড় হাতের অক্ষরে পরিবর্তন করে। এছাড়াও, যে ব্যবহারকারী মেসেজ ট্রিগার করেছেন তিনি আসলে মেসেজের প্রেরক কিনা তাও কোডটি চেক করে।

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onValueWritten} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
  // 1. Check whether authtype is admin. If it is, skip this operation.
  if (event.authType === "admin") {
    logger.log("Modification by admin detected. Skipping uppercase conversion.");
    return null;
  }

  // 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
  const snapshot = await event.data.after.ref.parent.child("senderId").get();
  const senderId = snapshot.val();

  // 3. Check if userID of sender of message = event.authid
  if (senderId !== event.authId) {
    logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
    return null;
  }

  // Grab the value that was written to the Realtime Database.
  const original = event.data.after.val();
  logger.log("Uppercasing", event.params.pushId, original);
  const uppercase = original.toUpperCase();

  // Return the promise to set the "uppercase" sibling node.
  return event.data.after.ref.parent.child("uppercase").set(uppercase);
});

Python

from firebase_functions import db_fn
from firebase_admin import initialize_app, db

initialize_app()

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
    # 1. Check whether authtype is admin. If it is, skip this operation.
    if event.auth_type == "admin":
        print("Admin user detected. Skipping.")
        return

    # 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
    parent_ref = db.reference(event.reference).parent
    sender_id = parent_ref.child("senderId").get()

    # 3. Check if userID of sender = event.auth_id
    if sender_id != event.auth_id:
        print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value and uppercase it
    original = event.data.after
    if not isinstance(original, str):
        return

    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent_ref.child("uppercase").set(upper)