استخدام حزمة SDK للمشرف مع SQL Connect

Firebase Admin SDK هي مجموعة من مكتبات الخادم التي تتيح لك التفاعل مع Firebase من بيئات ذات امتيازات لتنفيذ إجراءات مثل تنفيذ طلبات البحث والتعديلات على خدمة Firebase SQL Connect لإدارة البيانات المجمّعة وعمليات أخرى ذات أذونات مميزة وعالية المستوى وبيانات اعتماد منتحلة.

توفّر لك Admin SDK واجهة برمجة تطبيقات لاستدعاء العمليات في وضعَي القراءة والكتابة والقراءة فقط. باستخدام عمليات القراءة فقط، يمكنك تنفيذ وظائف إدارية لا يمكنها تعديل البيانات في قواعد البيانات، ما يمنحك راحة البال.

إعداد حزمة SDK للمشرف

لبدء استخدام Firebase SQL Connect على خادمك، عليك أولاً تثبيت Admin SDK وإعداده لـ Node.js.

إعداد مدير SDK في النصوص البرمجية

لبدء استخدام حزمة SDK، استورِد إضافات SQL Connect وحدِّد رقم تعريف خدمة مشروعك وموقعه الجغرافي.


import { initializeApp } from 'firebase-admin/app';
import { getDataConnect } from 'firebase-admin/data-connect';

// If you'd like to use OAuth2 flows and other credentials to log in,
// visit https://firebase.google.com/docs/admin/setup#initialize-sdk
// for alternative ways to initialize the SDK.

const app = initializeApp();

const dataConnect = getDataConnect({
    serviceId: 'serviceId',
    location: 'us-west2'
});

تصميم طلبات البحث وعمليات التعديل لاستخدامها مع Admin SDK

يكون Admin SDK مفيدًا لتنفيذ عمليات SQL Connect، مع مراعاة ما يلي:

فهم حزمة تطوير البرامج (SDK) وتوجيه التشغيل @auth(level: NO_ACCESS)

بما أنّ Admin SDK يعمل مع امتيازات، يمكنه تنفيذ أي من طلبات البحث والتعديلات بغض النظر عن مستويات الوصول التي تم ضبطها باستخدام توجيهات @auth، بما في ذلك مستوى NO_ACCESS.

إذا كنت تنظّم طلبات البحث والتعديلات الإدارية في ملفات المصدر .gql لاستيرادها إلى البرامج النصية الإدارية، إلى جانب عمليات العميل، تنصحك Firebase بوضع علامة على العمليات الإدارية بدون أي مستوى وصول إلى التفويض، أو ربما أن تكون أكثر وضوحًا وتضبطها على NO_ACCESS. وفي كلتا الحالتين، يمنع ذلك تنفيذ هذه العمليات من خلال البرامج أو في سياقات أخرى غير مميّزة.

استخدام حزمة SDK مع المحاكي SQL Connect

في بيئات النماذج الأولية والاختبار، قد يكون من المفيد تنفيذ عمليات إدخال البيانات وغيرها من العمليات على البيانات المحلية. تتيح لك السمة Admin SDK تبسيط سير العمل لأنّها يمكن أن تتجاهل المصادقة والتفويض في عمليات التنفيذ المحلية. (يمكنك أيضًا الموافقة صراحةً على الالتزام بإعدادات المصادقة والتفويض في عملياتك مع تسجيل الدخول باسم مستخدم آخر.)

تربط حِزم Firebase Admin SDK تلقائيًا بSQL Connect المحاكي عند ضبط متغيّر البيئة DATA_CONNECT_EMULATOR_HOST على النحو التالي:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

يمكنك الاطّلاع على ما يلي للحصول على مزيد من المعلومات:

تنفيذ عمليات المشرف

يتم توفير Admin SDK للعمليات المميزة على بياناتك المهمة.

توفّر حزمة SDK للمشرف ثلاث مجموعات من واجهات برمجة التطبيقات:

  • حِزم SDK للمشرفين تم إنشاؤها، وهي حِزم SDK آمنة من حيث النوع تم إنشاؤها من gqlتعريفاتك بنفس الطريقة التي تنشئ بها حِزم SDK للعملاء.
  • واجهة عامة لتنفيذ عمليات GraphQL عشوائية، حيث تنفّذ التعليمات البرمجية الاستعلامات وعمليات التعديل وتمرّرها إلى الطريقة executeGraphql للقراءة والكتابة أو الطريقة executeGraphqlRead للقراءة فقط.
  • واجهة متخصّصة لعمليات البيانات المجمّعة، تعرض بدلاً من طرق executeGraphql العامة طرقًا مخصّصة لعمليات التعديل: insert وinsertMany وupsert وupsertMany.

إدارة البيانات باستخدام حِزم SDK التي تم إنشاؤها

يمكنك إنشاء حِزم SDK للمشرف من تعريفات gql بالطريقة نفسها التي تنشئ بها حِزم SDK للعملاء.

تحتوي حزمة SDK للمشرف التي تم إنشاؤها على واجهات ووظائف تتوافق مع تعريفات gql، ويمكنك استخدامها لتنفيذ عمليات على قاعدة البيانات. على سبيل المثال، لنفترض أنّك أنشأت حزمة SDK لقاعدة بيانات أغاني، بالإضافة إلى طلب بحث getSongs:

import { initializeApp } from "firebase-admin/app";
import { getSongs } from "@dataconnect/admin-generated";

const adminApp = initializeApp();

const songs = await getSongs(
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

لتحديد إعدادات الموصّل، اتّبِع الخطوات التالية:

import { initializeApp } from "firebase-admin/app";
import { getDataConnect } from "firebase-admin/data-connect";
import {
  connectorConfig,
  getSongs,
} from "@dataconnect/admin-generated";

const adminApp = initializeApp();
const adminDc = getDataConnect(connectorConfig);

const songs = await getSongs(
  adminDc,
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

انتحال هوية مستخدم لم يتم إثبات هويته

تم تصميم حِزم تطوير البرامج (SDK) الخاصة بالمشرفين ليتم تشغيلها من بيئات موثوقة، وبالتالي يمكنها الوصول إلى قواعد البيانات بدون أي قيود.

عند تنفيذ عمليات عامة باستخدام حزمة SDK الخاصة بالمشرف، عليك تجنُّب تنفيذ العملية باستخدام امتيازات المشرف الكاملة (باتّباع مبدأ الحدّ الأدنى من الامتيازات). بدلاً من ذلك، يجب تنفيذ العملية إما كمستخدم منتحَل الهوية (راجِع القسم التالي) أو كمستخدم منتحَل الهوية غير مصادَق عليه. يمكن للمستخدمين غير المصادق عليهم تنفيذ العمليات التي تم وضع علامة PUBLIC عليها فقط.

في المثال أعلاه، يتم تنفيذ طلب البحث getSongs كمستخدم غير مصادق عليه.

انتحال هوية مستخدم

يمكنك أيضًا تنفيذ عمليات نيابةً عن مستخدمين محدّدين من خلال تمرير جزء من رمز مميّز Firebase Authentication أو كله في الخيار impersonate. ويجب على الأقل تحديد معرّف المستخدم في المطالبة الفرعية. (هذه هي القيمة نفسها التي يمكن الرجوع إليها في عمليات SQL Connect GraphQL، وهي قيمة الخادم auth.uid).

عند انتحال هوية مستخدم، لن تنجح العملية إلا إذا اجتازت بيانات المستخدم التي قدّمتها عمليات التحقّق من المصادقة المحدّدة في تعريف GraphQL.

إذا كنت تستدعي حزمة SDK التي تم إنشاؤها من نقطة نهاية يمكن الوصول إليها بشكل علني، من المهم جدًا أن تتطلب نقطة النهاية المصادقة وأن تتحقّق من سلامة رمز المصادقة قبل استخدامه لانتحال هوية مستخدم.

عند استخدام Cloud Functions القابلة للاستدعاء، يتم التحقّق تلقائيًا من رمز المصادقة المميز، ويمكنك استخدامه كما في المثال التالي:

import { HttpsError, onCall } from "firebase-functions/https";

export const callableExample = onCall(async (req) => {
    const authClaims = req.auth?.token;
    if (!authClaims) {
        throw new HttpsError("unauthenticated", "Unauthorized");
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

بخلاف ذلك، استخدِم طريقة Admin SDK verifyIdToken للتحقّق من صحة رمز المصادقة وفك تشفيره. على سبيل المثال، لنفترض أنّ نقطة النهاية تم تنفيذها كدالة HTTP عادية وأنّك مرّرت الرمز المميّز Firebase Authentication إلى نقطة النهاية باستخدام العنوان authorization، كما هو معتاد:

import { getAuth } from "firebase-admin/auth";
import { onRequest } from "firebase-functions/https";

const auth = getAuth();

export const httpExample = onRequest(async (req, res) => {
    const token = req.header("authorization")?.replace(/^bearer\s+/i, "");
    if (!token) {
        res.sendStatus(401);
        return;
    }
    let authClaims;
    try {
        authClaims = await auth.verifyIdToken(token);
    } catch {
        res.sendStatus(401);
        return;
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

يجب تحديد رقم تعريف مستخدم لم يتم الحصول عليه من مصدر يمكن التحقّق منه فقط عند تنفيذ مهام إدارية حقيقية، مثل نقل البيانات، من بيئة آمنة لا يمكن الوصول إليها بشكل علني:

// Never do this if end users can initiate execution of the code!
const favoriteSongs = await getMyFavoriteSongs(
  undefined,
  { impersonate: { authClaims } }
);

التشغيل مع إمكانية الوصول غير المحدود

إذا كنت بصدد تنفيذ عملية تتطلّب أذونات على مستوى المشرف، احذف المَعلمة impersonate من الطلب:

await upsertSong(adminDc, {
  title: songTitle_one,
  instrumentsUsed: [Instrument.VOCAL],
});

يتمتع الإجراء الذي يتم استدعاؤه بهذه الطريقة بإذن وصول كامل إلى قاعدة البيانات. إذا كانت لديك طلبات بحث أو عمليات تغيير مخصّصة لأغراض الإدارة فقط، عليك تحديدها باستخدام التوجيه @auth(level: NO_ACCESS). ويضمن ذلك أنّه يمكن فقط للمتصلين على مستوى المشرف تنفيذ هذه العمليات.

إدارة البيانات باستخدام طرق executeGraphql

إذا كنت بحاجة إلى تنفيذ عمليات لمرة واحدة لم تحدّد لها عمليات تغيير أو طلبات بحث gql، يمكنك استخدام الطريقة executeGraphql أو الطريقة executeGraphqlRead للقراءة فقط.

تنفيذ عمليات علائقية مخصّصة متداخلة (واحد إلى متعدد وواحد إلى واحد) باستخدام executeGraphql

عند استدعاء عمليات من حزمة تطوير البرامج (SDK) الخاصة بالمشرفين في بيئة ذات امتيازات، لن تحتاج إلى تحديد التوجيه @allow في متغيراتك لإجراء عمليات إدراج عادية (غير متداخلة) (مثل إدراج سجلّات فردية خاصة بالأفلام أو الممثلين).

ومع ذلك، لإجراء عمليات إدراج علائقية متداخلة (مثل إدراج فيلم مع مراجعاته الأولية أو مستخدم وملفه الشخصي)، يجب أن تظل تعرّف التوجيه @allow في تعريف المتغير للسماح بشكل ثابت بالحقول العلائقية المتداخلة داخل مخطط المتغير المجمَّع.

// Custom GraphQL mutation with nested relational insert (one-to-many)
const customMutation = `
  mutation CustomBatchInsert(
    $data: [Movie_Data!]!
    @allow(fields: "title genre reviews_on_movie { rating reviewText }")
  ) {
    movie_insertMany(data: $data)
  }
`;
const variables = {
  data: [
    {
      title: "Interstellar",
      genre: "Sci-Fi",
      reviews_on_movie: [
        {
          rating: 5,
          reviewText: "Visually stunning and emotionally powerful.",
          user: { id: "user-789" }
        }
      ]
    }
  ]
};
const response = await dataConnect.executeGraphql(customMutation, { variables });
// Custom GraphQL mutation with a one-to-one nested relational insert
const customMutationOneToOne = `
  mutation CustomBatchInsertOneToOne(
    $data: [User_Data!]!
    @allow(fields: "name profile_on_user { bio }")
  ) {
    user_insertMany(data: $data)
  }
`;
const variablesOneToOne = {
  data: [
    {
      name: "Alice",
      profile_on_user: {
        bio: "Avid moviegoer and critic."
      }
    }
  ]
};
const responseOneToOne = await dataConnect.executeGraphql(customMutationOneToOne, { variables: variablesOneToOne });

انتحال هوية مستخدم لم يتم إثبات هويته

عند تنفيذ عمليات عامة باستخدام حزمة SDK الخاصة بالمشرف، عليك تجنُّب تنفيذ العملية باستخدام امتيازات المشرف الكاملة (باتّباع مبدأ الحدّ الأدنى من الامتيازات). بدلاً من ذلك، يجب تنفيذ العملية إما كمستخدم منتحَل الهوية (راجِع القسم التالي) أو كمستخدم منتحَل الهوية غير مصادَق عليه. يمكن للمستخدمين غير المصادق عليهم تنفيذ العمليات التي تم وضع علامة PUBLIC عليها فقط.

// Query to get posts, with authentication level PUBLIC
const queryGetPostsImpersonation = `
    query getPosts @auth(level: PUBLIC) {
        posts {
          description
        }
    }`;

// Attempt to access data as an unauthenticated user
const optionsUnauthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        unauthenticated: true
    }
};

// executeGraphql with impersonated unauthenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetPostsImpersonation, optionsUnauthenticated);

انتحال هوية مستخدم

هناك أيضًا حالات استخدام تريد فيها أن تعدّل البرامج النصية بيانات المستخدم استنادًا إلى بيانات اعتماد محدودة، نيابةً عن مستخدم معيّن. يتوافق هذا النهج مع مبدأ الحد الأدنى من الأذونات المميزة.

لاستخدام هذه الواجهة، اجمع المعلومات من رمز مميّز مخصّص للمصادقة باستخدام JWT يتّبع تنسيق الرمز المميز Authentication. يمكنك أيضًا الاطّلاع على دليل الرموز المميّزة المخصّصة.

// Get the current user's data
const queryGetUserImpersonation = `
    query getUser @auth(level: USER) {
        user(key: {uid_expr: "auth.uid"}) {
            id,
            name
        }
    }`;

// Impersonate a user with the specified auth claims
const optionsAuthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        authClaims: {
            sub: 'QVBJcy5ndXJ1'
        }
    }
};

// executeGraphql with impersonated authenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetUserImpersonation, optionsAuthenticated);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

استخدام بيانات اعتماد إدارية

إذا كنت بصدد تنفيذ عملية تتطلّب أذونات على مستوى المشرف، احذف المَعلمة impersonate من الطلب:

// User can be publicly accessible, or restricted to admins
const query = "query getProfile(id: AuthID) { user(id: $id) { id name } }";

interface UserData {
  user: {
    id: string;
    name: string;
  };
}

export interface UserVariables {
  id: string;
}

const options:GraphqlOptions<UserVariables> = { variables: { id: "QVBJcy5ndXJ1" } };

// executeGraphql
const gqlResponse = await dataConnect.executeGraphql<UserData, UserVariables>(query, options);

// executeGraphqlRead (similar to previous sample but only for read operations)
const gqlResponse = await dataConnect.executeGraphqlRead<UserData, UserVariables>(query, options);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

يتمتع الإجراء الذي يتم استدعاؤه بهذه الطريقة بإذن وصول كامل إلى قاعدة البيانات. إذا كانت لديك طلبات بحث أو عمليات تغيير مخصّصة لأغراض الإدارة فقط، عليك تحديدها باستخدام التوجيه @auth(level: NO_ACCESS). ويضمن ذلك أنّه يمكن فقط للمتصلين على مستوى المشرف تنفيذ هذه العمليات.

تنفيذ عمليات إدراج البيانات المجمّعة

تنصحك Firebase باستخدام Admin SDK لإجراء عمليات البيانات المجمّعة على قواعد البيانات المتاحة للاستخدام.

توفّر حزمة تطوير البرامج الطرق التالية للتعامل مع البيانات المجمّعة. من الوسيطات المقدَّمة، تنشئ كل طريقة عملية تعديل في GraphQL وتنفّذها.


// Methods of the bulk operations API
// dc is a SQL Connect admin instance from getDataConnect

const resp = await dc.insert("movie" /*table name*/, data[0]);
const resp = await dc.insertMany("movie" /*table name*/, data);
const resp = await dc.upsert("movie" /*table name*/, data[0]);
const resp = await dc.upsertMany("movie" /*table name*/, data);

عمليات ربط متداخلة (واحد إلى متعدد وواحد إلى واحد)

تتيح السمة Admin SDK البيانات الارتباطية في العمليات المجمّعة. كما هو الحال مع عمليات التغيير المخصّصة، استخدِم مصفوفة من العناصر للعلاقات بين عنصر واحد وعدة عناصر، وعنصرًا واحدًا للعلاقات بين عنصر واحد وعنصر واحد:

// Example of inserting a movie with nested reviews (one-to-many)
const moviesData = [
  {
    title: "Interstellar",
    genre: "Sci-Fi",
    reviews_on_movie: [
      {
        rating: 5,
        reviewText: "Visually stunning and emotionally powerful.",
        user: { id: "user-789" }
      }
    ]
  }
];

// Atomically insert movies and their reviews in a single database round-trip.
const response = await dc.insertMany("movie", moviesData);
// Example of inserting a user with a nested profile (one-to-one)
const usersData = [
  {
    name: "Alice",
    profile_on_user: {
      bio: "Avid moviegoer and critic."
    }
  }
];

// Atomically insert users and their profiles.
const userResponse = await dc.insertMany("user", usersData);

ملاحظات حول الأداء في العمليات المجمّعة

سيؤدي كل طلب إلى الخلفية إلى إجراء رحلة واحدة إلى Cloud SQL، لذا كلما زاد عدد الطلبات المجمّعة، زادت سرعة معالجة البيانات.

ومع ذلك، كلما زاد حجم الدفعة، طالت عبارة SQL التي تم إنشاؤها. عند بلوغ الحد الأقصى لطول عبارة SQL في PostgreSQL، سيظهر لك خطأ.

في الواقع، جرِّب أحجامًا مختلفة للعثور على حجم الدفعة المناسب لعبء العمل.

ما هي الخطوات التالية؟