الإنشاء باستخدام Firebase Data Connect

1- قبل البدء

تطبيق FriendlyMovies

في هذا الدليل التعليمي حول الرموز البرمجية، ستدمج Firebase Data Connect مع قاعدة بيانات Cloud SQL لإنشاء تطبيق ويب لمراجعة الأفلام. يعرض التطبيق المكتمل كيفية تبسيط Firebase Data Connect لعملية إنشاء التطبيقات المستندة إلى SQL. وتشمل هذه الميزات ما يلي:

  • المصادقة: يمكنك تنفيذ مصادقة مخصّصة لطلبات البحث وعمليات التحويل في تطبيقك، ما يضمن عدم تمكّن سوى المستخدمين المعتمَدين من التفاعل مع بياناتك.
  • مخطّط GraphQL: يمكنك إنشاء بنى البيانات وإدارتها باستخدام مخطّط GraphQL مرن مخصّص لتلبية احتياجات تطبيق الويب لمراجعة الأفلام.
  • طلبات البحث وعمليات التحويل في SQL: استرداد البيانات وتعديلها وإدارتها في Cloud SQL باستخدام طلبات البحث وعمليات التحويل المستندة إلى GraphQL
  • البحث المتقدم باستخدام مطابقة السلسلة الجزئية: استخدِم الفلاتر وخيارات البحث للعثور على الأفلام استنادًا إلى حقول مثل العنوان أو الوصف أو العلامات.
  • اختياري: دمج البحث بالاستناد إلى المتجهات: يمكنك إضافة وظيفة البحث عن المحتوى باستخدام ميزة البحث بالاستناد إلى المتجهات في Firebase Data Connect لتوفير تجربة ثرية للمستخدم استنادًا إلى الإدخالات والإعدادات المفضّلة.

المتطلبات الأساسية

ستحتاج إلى فهم أساسي للغة JavaScript.

المعلومات التي ستتعرّف عليها

  • إعداد Firebase Data Connect باستخدام المحاكيات المحلية
  • تصميم مخطّط بيانات باستخدام Data Connect وGraphQL
  • كتابة طلبات بحث وعمليات تحويل مختلفة واختبارها لتطبيق مراجعات الأفلام
  • تعرَّف على كيفية إنشاء Firebase Data Connect لحزمة تطوير البرامج (SDK) واستخدامها في التطبيق.
  • يمكنك نشر المخطّط وإدارة قاعدة البيانات بكفاءة.

المتطلبات

  • Git
  • Visual Studio Code
  • ثبِّت Node.js باستخدام nvm-windows (لنظام التشغيل Windows) أو nvm (لنظام التشغيل macOS/Linux).
  • أنشئ مشروعًا على Firebase في وحدة تحكّم Firebase، إذا لم يسبق لك ذلك.
  • (اختياري) لاستخدام ميزة البحث بالاستناد إلى شكل هندسي، عليك ترقية مشروعك إلى خطة Blaze.

إعداد بيئة التطوير

سيرشدك هذا القسم خلال عملية إعداد البيئة لبدء إنشاء تطبيق مراجعات الأفلام باستخدام Firebase Data Connect.

الخطوة 1: استنساخ مستودع المشروع

ابدأ من خلال استنساخ مستودع المشروع وتثبيت التبعيات المطلوبة:

git clone https://github.com/firebaseextended/codelab-dataconnect-web
cd codelab-dataconnect-web
cd ./app && npm i
npm run dev
  1. بعد تنفيذ هذه الأوامر، افتح http://localhost:5173 في المتصفّح للاطّلاع على تطبيق الويب الذي يتم تشغيله على الجهاز. ويكون هذا القسم هو الواجهة الأمامية لإنشاء تطبيق مراجعة الأفلام والتفاعل مع ميزاته.

93f6648a2532c606.png

الخطوة 2: فتح المشروع في Visual Studio Code

افتح مجلد codelab-dataconnect-web الذي تم نسخه باستخدام Visual Studio Code. في هذا القسم، يمكنك تحديد المخطّط وكتابة طلبات البحث واختبار وظائف التطبيق.

الخطوة 3: تثبيت إضافة Firebase Data Connect في Visual Studio

لاستخدام ميزات Data Connect، ثبِّت إضافة Visual Studio لـ Firebase Data Connect.أو ثبِّتها من Visual Studio Code Marketplace أو ابحث عنها في VS Code.

  1. أو: ثبِّت الإضافة من Visual Studio Code Marketplace أو ابحث عنها في VS Code.

b03ee38c9a81b648.png

الخطوة 4: إنشاء مشروع على Firebase

انتقِل إلى وحدة تحكّم Firebase لإنشاء مشروع جديد على Firebase إذا لم يكن لديك مشروع حالي. بعد ذلك، في إضافة Firebase Data Connect في VSCode:

  • انقر على الزر تسجيل الدخول.
  • انقر على ربط مشروع Firebase واختَر المشروع الذي أنشأته في "وحدة تحكُّم Firebase".

4bb2fbf8f9fac29b.png

الخطوة 5: بدء محاكيات Firebase

في إضافة Firebase Data Connect في VSCode، انقر على "بدء المحاكيات" وتأكَّد من أنّ المحاكيات تعمل في المحطة الطرفية.

6d3d95f4cb708db1.png

2- مراجعة قاعدة بيانات التطبيق الأساسي

في هذا القسم، ستستكشف الجوانب الرئيسية لقاعدة بيانات التطبيق الأساسية. على الرغم من أنّ التطبيق لا يتضمّن بعض الوظائف، إلا أنّه من المفيد فهم الهيكل العام.

بنية المجلدات والملفات

في ما يلي نظرة عامة سريعة على بنية مجلدات التطبيق وملفاته:

dataconnect/

يحتوي على إعدادات Firebase Data Connect وأدوات الربط (التي تحدّد طلبات البحث وعمليات التحويل) وملفات المخططات.

  • schema/schema.gql: لتحديد مخطّط GraphQL
  • connector/queries.gql: طلبات البحث المطلوبة في تطبيقك
  • connector/mutations.gql: التعديلات المطلوبة في تطبيقك
  • connector/connector.yaml: ملف الإعداد لإنشاء حزمة SDK

app/src/

يحتوي على منطق التطبيق والتفاعل مع Firebase Data Connect.

  • firebase.ts: الإعداد للربط بتطبيق Firebase في وحدة التحكّم
  • lib/dataconnect-sdk/: يحتوي هذا المجلد على حزمة SDK التي تم إنشاؤها. يمكنك تعديل موقع إنشاء حِزم SDK في ملف connector/connector.yaml، وسيتم إنشاء حِزم SDK تلقائيًا في أي وقت تحدّد فيه طلب بحث أو تعديلًا.

3- تحديد مخطّط لمراجعة فيلم

في هذا القسم، ستحدِّد في مخطّط الهيكل والعلاقات بين الكيانات الرئيسية في تطبيق الأفلام. يتمّ ربط كيانات مثل Movie وUser وActor وReview بجداول قاعدة البيانات، مع إنشاء العلاقات باستخدام Firebase Data Connect وتوجيهات مخطّط GraphQL. بعد الانتهاء من ذلك، سيكون تطبيقك جاهزًا للتعامل مع كل شيء، بدءًا من البحث عن الأفلام الأعلى تقييمًا والفلترة حسب النوع، إلى السماح للمستخدمين بكتابة مراجعات أو وضع علامة على المحتوى المفضّل أو استكشاف الأفلام المشابهة أو العثور على أفلام مقترَحة استنادًا إلى النص الذي يُدخلونه من خلال البحث بالاستناد إلى المتجهات.

الكيانات والعلاقات الأساسية

يحتوي نوع Movie على تفاصيل رئيسية، مثل العنوان والنوع والعلامات، التي يستخدمها التطبيق في عمليات البحث والملفات الشخصية للأفلام. يتتبّع النوع User تفاعلات المستخدمين، مثل المراجعات والعناصر المفضّلة. Reviews ربط المستخدمين بالأفلام، والسماح للتطبيق بعرض التقييمات والملاحظات التي ينشئها المستخدمون

تجعل العلاقات بين الأفلام والممثلين والمستخدمين التطبيق أكثر ديناميكية. يساعد جدول الربط MovieActor في عرض تفاصيل الممثلين وأعمالهم السينمائية. يسمح نوع FavoriteMovie للمستخدمين بإضافة الأفلام إلى قائمة المفضّلة، ما يتيح للتطبيق عرض قائمة مخصّصة بالمفضّلة وتسليط الضوء على الاختيارات الرائجة.

جدول الأفلام

يحدِّد نوع Movie البنية الرئيسية لكيان الفيلم، بما في ذلك حقول مثل title وgenre وreleaseYear وrating.

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type Movie
  @table {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
}

الأفكار الرئيسية المستخلصة:

  • id: معرّف UUID فريد لكل فيلم، يتم إنشاؤه باستخدام @default(expr: "uuidV4()").

جدول MovieMetadata

ينشئ نوع MovieMetadata علاقة بين عنصرَين من النوع Movie. ويتضمن بيانات إضافية، مثل مخرج الفيلم.

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type MovieMetadata
  @table {
  # @ref creates a field in the current table (MovieMetadata)
  # It is a reference that holds the primary key of the referenced type
  # In this case, @ref(fields: "movieId", references: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String
}

الأفكار الرئيسية المستخلصة:

  • فيلم! @ref: تشير إلى نوع Movie، ما يؤدّي إلى إنشاء علاقة مفتاح خارجي.

جدول الجهات الفاعلة

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type Actor @table {
  id: UUID!
  imageUrl: String! 
  name: String! @col(name: "name", dataType: "varchar(30)")
}

يمثّل نوع Actor ممثلًا في قاعدة بيانات الأفلام، حيث يمكن أن يكون كل ممثل جزءًا من أفلام متعددة، ما يشكّل علاقة بين عناصر متعددة.

جدول "ممثلو الأفلام"

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type MovieActor @table(key: ["movie", "actor"]) {
  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie!
  # movieId: UUID! <- this is created by the implied @ref, see: implicit.gql

  actor: Actor!
  # actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql

  role: String! # "main" or "supporting"
}

الأفكار الرئيسية المستخلصة:

  • movie: يشير إلى نوع Movie، وينشئ بشكل ضمني مفتاحًا أجنبيًا movieId: UUID!.
  • الفاعل: يشير إلى نوع الفاعل، وينشئ بشكل ضمني مفتاحًا خارجيًا actorId: UUID!.
  • role: لتحديد دور الممثل في الفيلم (مثل "main" أو "supporting").

جدول المستخدمين

يحدِّد نوع User عنصر مستخدم يتفاعل مع الأفلام من خلال كتابة مراجعات أو إضافة الأفلام إلى المفضّلة.

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type User
  @table {
  id: String! @col(name: "auth_uid")
  username: String! @col(dataType: "varchar(50)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user 
  # movies_via_Review
}

جدول FavoriteMovie

نوع FavoriteMovie هو جدول ربط يعالج العلاقات بين جهات متعددة بين المستخدمين والأفلام أو الممثلين المفضّلين لديهم. يربط كل جدول User بأحد Movie.

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type FavoriteMovie
  @table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
  # @ref is implicit
  user: User!
  movie: Movie!
}

الأفكار الرئيسية المستخلصة:

  • movie: يشير إلى نوع Movie، وينشئ بشكل ضمني مفتاحًا أجنبيًا movieId: UUID!.
  • user: يشير إلى نوع المستخدم، وينشئ بشكل ضمني مفتاحًا خارجيًا userId: UUID!.

جدول المراجعة

يمثّل نوع المراجعة عنصر المراجعة ويربط نوعَي "المستخدم" و"الفيلم" في علاقة بين أطراف متعددة (يمكن لمستخدم واحد نشر العديد من المراجعات، ويمكن أن يتضمّن كل فيلم العديد من المراجعات).

انسخ مقتطف الرمز والصقه في ملف dataconnect/schema/schema.gql:

type Review @table(name: "Reviews", key: ["movie", "user"]) {
  id: UUID! @default(expr: "uuidV4()")
  user: User!
  movie: Movie!
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

الأفكار الرئيسية المستخلصة:

  • user: يشير إلى المستخدم الذي كتب المراجعة.
  • movie: تشير إلى الفيلم الذي تتم مراجعته.
  • reviewDate: يتم ضبطها تلقائيًا على الوقت الذي تم فيه إنشاء المراجعة باستخدام @default(expr: "request.time").

الحقول والإعدادات التلقائية

يستخدم المخطّط عبارات مثل @default(expr: "uuidV4()") لإنشاء معرّفات وطابعات زمنية فريدة تلقائيًا. على سبيل المثال، يتم ملء حقل id في نوعَي "الفيلم" و"المراجعة" تلقائيًا بمعرّف فريد عالمي (UUID) عند إنشاء سجلّ جديد.

بعد تحديد المخطّط، أصبح تطبيق الأفلام لديك يستند إلى قاعدة صلبة لهيكل البيانات والعلاقة بينها.

4. استرجاع أحدث الأفلام والأفلام الأكثر رواجًا

تطبيق FriendlyMovies

في هذا القسم، ستُدرج بيانات أفلام وهمية في المحاكيات المحلية، ثم ستنفِّذ الموصّلات (طلبات البحث) ورمز TypeScript لاستدعاء هذه الموصّلات في تطبيق الويب. وفي النهاية، سيتمكّن تطبيقك من جلب أحدث الأفلام وأفضلها تقييمًا من قاعدة البيانات وعرضها ديناميكيًا.

إدراج بيانات وهمية عن الأفلام والممثلين والمراجعات

  1. في VSCode، افتح dataconnect/moviedata_insert.gql. تأكَّد من تشغيل المحاكيات في إضافة Firebase Data Connect.
  2. من المفترض أن يظهر لك زر تشغيل (على الجهاز) في أعلى الملف. انقر على هذا الخيار لإدراج بيانات الفيلم النموذجية في قاعدة بياناتك.

e424f75e63bf2e10.png

  1. راجِع المحطة الطرفية تنفيذ ربط البيانات للتأكّد من إضافة البيانات بنجاح.

e0943d7704fb84ea.png

تنفيذ الموصِّل

  1. فتح dataconnect/movie-connector/queries.gql يمكنك العثور على طلب بحث ListMovies أساسي في التعليقات:
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

يعرض طلب البحث هذا جميع الأفلام وتفاصيلها (مثل رقم التعريف والعنوان وسنة الإصدار). ومع ذلك، لا يتم ترتيب الأفلام.

  1. استبدِل طلب البحث ListMovies بالطلب أدناه لإضافة خيارات الترتيب والحدّ:
# List subset of fields for movies
query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) {
  movies(
    orderBy: [
      { rating: $orderByRating },
      { releaseYear: $orderByReleaseYear }
    ]
    limit: $limit
  ) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

انقر على الزر تشغيل (محلي) لتنفيذ طلب البحث في قاعدة البيانات المحلية. يمكنك أيضًا إدخال متغيّرات الطلب في لوحة الإعدادات قبل التشغيل.

c4d947115bb11b16.png

الأفكار الرئيسية المستخلصة:

  • movies():‎ حقل طلب بحث GraphQL لاسترداد بيانات الأفلام من قاعدة البيانات
  • orderByRating: مَعلمة لترتيب الأفلام حسب التقييم (تصاعديًا/تنازليًا)
  • orderByReleaseYear: مَعلمة لترتيب الأفلام حسب عام الإصدار (تصاعديًا/تنازليًا)
  • limit: تحدّ من عدد الأفلام التي يتم عرضها.

دمج طلبات البحث في تطبيق الويب

في هذا الجزء، ستستخدم طلبات البحث المحدّدة في القسم السابق في تطبيق الويب. تعمل محاكيات Firebase Data Connect على إنشاء حِزم تطوير البرامج (SDK) استنادًا إلى المعلومات الواردة في ملفات ‎ .gql (schema.gql وqueries.gql وmutations.gql) وconnector.yaml. ويمكن استدعاء حِزم تطوير البرامج هذه مباشرةً في تطبيقك.

  1. في MovieService (app/src/lib/MovieService.tsxأزِل التعليق عن عبارة الاستيراد في أعلى الصفحة:
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

الوظيفة listMovies ونوع الاستجابة ListMoviesData والعنصر المحدَّد OrderDirection كلها حِزم تطوير برامج (SDK) تم إنشاؤها بواسطة محاكيات Firebase Data Connect استنادًا إلى المخطّط وطلبات البحث التي حدّدتها سابقًا .

  1. استبدِل الدالتَين handleGetTopMovies وhandleGetLatestMovies بالرمز البرمجي التالي:
// Fetch top-rated movies
export const handleGetTopMovies = async (
  limit: number
): Promise<ListMoviesData["movies"] | null> => {
  try {
    const response = await listMovies({
      orderByRating: OrderDirection.DESC,
      limit,
    });
    return response.data.movies;
  } catch (error) {
    console.error("Error fetching top movies:", error);
    return null;
  }
};

// Fetch latest movies
export const handleGetLatestMovies = async (
  limit: number
): Promise<ListMoviesData["movies"] | null> => {
  try {
    const response = await listMovies({
      orderByReleaseYear: OrderDirection.DESC,
      limit,
    });
    return response.data.movies;
  } catch (error) {
    console.error("Error fetching latest movies:", error);
    return null;
  }
};

الأفكار الرئيسية المستخلصة:

  • listMovies: دالة يتم إنشاؤها تلقائيًا تستدعي طلب البحث listMovies لاسترداد قائمة بالأفلام. وتتضمّن خيارات لترتيب المحتوى حسب التقييم أو سنة الإصدار وحصر عدد النتائج.
  • ListMoviesData: نوع النتيجة المستخدَم لعرض أهم 10 أفلام وأحدث الأفلام على الصفحة الرئيسية.

أمثلة واقعية

أعِد تحميل تطبيق الويب للاطّلاع على طلب البحث أثناء تنفيذه. تعرض الصفحة الرئيسية الآن قائمة الأفلام بشكل ديناميكي، وتسترجع البيانات مباشرةً من قاعدة البيانات المحلية. ستظهر لك أحدث الأفلام وتلك التي حصلت على أعلى التقييمات بسلاسة، استنادًا إلى البيانات التي ضبطتها للتو.

5- عرض تفاصيل الفيلم والممثل

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

ac7fefa7ff779231.png

تنفيذ الموصّلات

  1. افتح dataconnect/movie-connector/queries.gql في مشروعك.
  2. أضِف طلبات البحث التالية لاسترداد تفاصيل الأفلام والممثلين:
# Get movie by id
query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
 movie(id: $id) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    description
    tags
    metadata: movieMetadatas_on_movie {
      director
    }
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      name
      imageUrl
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      name
      imageUrl
    }
    reviews: reviews_on_movie {
      id
      reviewText
      reviewDate
      rating
      user {
        id
        username
      }
    }
  }
 }

# Get actor by id
query GetActorById($id: UUID!) @auth(level: PUBLIC) {
  actor(id: $id) {
    id
    name
    imageUrl
    mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      title
      genre
      tags
      imageUrl
    }
    supportingActors: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      title
      genre
      tags
      imageUrl
    }
  }
}
  1. احفظ التغييرات وراجِع طلبات البحث.

الأفكار الرئيسية المستخلصة:

  • movie()/actor(): حقول طلب بحث GraphQL لجلب فيلم أو ممثل واحد من جدول "الأفلام" أو "الممثلون"
  • _on_: يتيح هذا الوصول المباشر إلى الحقول من نوع مرتبط له علاقة مفتاح خارجي. على سبيل المثال، يُستخدَم reviews_on_movie لاسترداد جميع المراجعات المرتبطة بفيلم معيّن.
  • _via_: يُستخدَم للتنقّل في العلاقات بين عناصر متعددة من خلال جدول ربط. على سبيل المثال، يصل العمود actors_via_MovieActor إلى نوع "الممثل" من خلال جدول الربط "الممثل في الفيلم"، وفلترَت الحالة where الممثلين استنادًا إلى دورهم (مثل "main" أو "supporting").

في لوحة تنفيذ "ربط البيانات"، يمكنك اختبار طلب البحث عن طريق إدخال معرّفات وهمية، مثل:

{"id": "550e8400-e29b-41d4-a716-446655440000"}

انقر على تشغيل (محلي) للملف GetMovieById لاسترداد تفاصيل "Quantum Paradox" (الفيلم الساخر الذي يرتبط به رقم التعريف أعلاه).

1b08961891e44da2.png

دمج طلبات البحث في تطبيق الويب

  1. في MovieService (app/src/lib/MovieService.tsxأزِل التعليق عن عمليات الاستيراد التالية:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. استبدِل الدالتَين handleGetMovieById وhandleGetActorById بالرمز البرمجي التالي:
// Fetch movie details by ID
export const handleGetMovieById = async (
  movieId: string
) => {
  try {
    const response = await getMovieById({ id: movieId });
    if (response.data.movie) {
      return response.data.movie;
    }
    return null;
  } catch (error) {
    console.error("Error fetching movie:", error);
    return null;
  }
};

// Calling generated SDK for GetActorById
export const handleGetActorById = async (
  actorId: string
): Promise<GetActorByIdData["actor"] | null> => {
  try {
    const response = await getActorById({ id: actorId });
    if (response.data.actor) {
      return response.data.actor;
    }
    return null;
  } catch (error) {
    console.error("Error fetching actor:", error);
    return null;
  }
};

الأفكار الرئيسية المستخلصة:

  • getMovieById / getActorById: هذه وظائف يتم إنشاؤها تلقائيًا تستدعي طلبات البحث التي حدّدتها، ما يؤدي إلى استرداد معلومات مفصّلة عن فيلم أو ممثل معيّن.
  • GetMovieByIdData / GetActorByIdData: هذه هي أنواع النتائج المستخدَمة لعرض تفاصيل الأفلام والممثلين في التطبيق.

أمثلة واقعية

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

6- التعامل مع مصادقة المستخدم

في هذا القسم، ستنفِّذ وظيفة تسجيل دخول المستخدم وتسجيل خروجه باستخدام Firebase Authentication. ستستخدم أيضًا بيانات Firebase Authentication لاسترداد بيانات المستخدمين أو تعديلها مباشرةً في Firebase DataConnect، ما يضمن إدارة المستخدمين بأمان داخل تطبيقك.

9890838045d5a00e.png

تنفيذ الموصّلات

  1. افتح mutations.gql في dataconnect/movie-connector/.
  2. أضِف الإجراء التالي لإنشاء المستخدم الحالي الذي تم مصادقة بياناته أو تعديله:
# Create or update the current authenticated user
mutation UpsertUser($username: String!) @auth(level: USER) {
  user_upsert(
    data: {
      id_expr: "auth.uid"
      username: $username
    }
  )
}

الفكرة الرئيسية المستخلصة:

  • id_expr: "auth.uid": يستخدم هذا الإجراء auth.uid الذي تقدّمه Firebase Authentication مباشرةً، وليس المستخدم أو التطبيق، ما يضيف طبقة إضافية من الأمان من خلال ضمان معالجة معرّف المستخدم بشكل آمن وتلقائي.

بعد ذلك، افتح queries.gql في dataconnect/movie-connector/.

أضِف طلب البحث التالي لاسترجاع المستخدم الحالي:

# Get user by ID
query GetCurrentUser @auth(level: USER) {
  user(key: { id_expr: "auth.uid" }) {
    id
    username
    reviews: reviews_on_user {
      id
      rating
      reviewDate
      reviewText
      movie {
        id
        title
      }
    }
    favoriteMovies: favorite_movies_on_user {
      movie {
        id
        title
        genre
        imageUrl
        releaseYear
        rating
        description
        tags
        metadata: movieMetadatas_on_movie {
          director
        }
      }
    }
  }
}

الخلاصات الرئيسية:

  • auth.uid: يتم استرداد هذا الرمز مباشرةً من "مصادقة Firebase"، ما يضمن الوصول الآمن إلى البيانات الخاصة بالمستخدم.
  • _on_ الحقول: تمثّل هذه الحقول الجداول التي يتمّ دمجها:
  • reviews_on_user: تُستخدَم هذه السمة لاسترداد جميع المراجعات المرتبطة بالمستخدم، بما في ذلك رقم تعريف الفيلم وعنوانه.
  • favorite_movies_on_user: استرداد جميع الأفلام التي وضع المستخدم علامة عليها باعتبارها مفضّلة، بما في ذلك معلومات تفصيلية مثل النوع وسنة الإصدار والتقييم والبيانات الوصفية

دمج طلبات البحث في تطبيق الويب

  1. في MovieService (app/src/lib/MovieService.tsx)، أزِل التعليقات من عمليات الاستيراد التالية:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. استبدِل الدالتَين handleAuthStateChange وhandleGetCurrentUser بالرمز البرمجي التالي:
// Handle user authentication state changes and upsert user
export const handleAuthStateChange = (
  auth: any,
  setUser: (user: User | null) => void
) => {
  return onAuthStateChanged(auth, async (user) => {
    if (user) {
      setUser(user);
      const username = user.email?.split("@")[0] || "anon";
      await upsertUser({ username });
    } else {
      setUser(null);
    }
  });
};

// Fetch current user profile
export const handleGetCurrentUser = async (): Promise<
  GetCurrentUserData["user"] | null
> => {
  try {
    const response = await getCurrentUser();
    return response.data.user;
  } catch (error) {
    console.error("Error fetching user profile:", error);
    return null;
  }
};

الأفكار الرئيسية المستخلصة:

  • handleAuthStateChange: تستمع هذه الدالة إلى تغييرات حالة المصادقة. عندما يسجّل مستخدم الدخول، يتم ضبط بيانات المستخدم واستدعاء عملية upsertUser لإنشاء معلومات المستخدم أو تعديلها في قاعدة البيانات.
  • handleGetCurrentUser: تُستخدَم هذه السمة لاسترداد الملف الشخصي للمستخدم الحالي باستخدام طلب البحث getCurrentUser، الذي يستردّ مراجعات المستخدم وأفلامه المفضّلة.

أمثلة واقعية

الآن، انقر على الزر "تسجيل الدخول باستخدام حساب Google" في شريط التنقل. يمكنك تسجيل الدخول باستخدام محاكي Firebase Auth. بعد تسجيل الدخول، انقر على "ملفي الشخصي". سيكون هذا القسم فارغًا في الوقت الحالي، ولكنك أنشأت الأساس لمعالجة البيانات الخاصة بالمستخدمين في تطبيقك.

7- تنفيذ تفاعلات المستخدِمين

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

b3d0ac1e181c9de9.png

تنفيذ الموصّلات

  1. افتح mutations.gql في dataconnect/movie-connector/.
  2. أضِف عمليات التحويل التالية للتعامل مع الأفلام المفضّلة:
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

الأفكار الرئيسية المستخلصة:

  • userId_expr: "auth.uid": يستخدم auth.uid الذي تقدّمه مباشرةً خدمة "مصادقة Firebase"، ما يضمن الوصول إلى بيانات المستخدم الذي تمّت مصادقة هويته أو تعديلها فقط.
  1. بعد ذلك، افتح queries.gql في dataconnect/movie-connector/.
  2. أضِف طلب البحث التالي للتحقّق مما إذا كان الفيلم مُدرَجًا في قائمة المفضّلة:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}

الأفكار الرئيسية المستخلصة:

  • auth.uid: تضمن الوصول الآمن إلى البيانات الخاصة بالمستخدم باستخدام ميزة "مصادقة Firebase".
  • favorite_movie: تتحقّق هذه الدالة من جدول الربط favorite_movies لمعرفة ما إذا كان المستخدم الحالي قد وضع علامة على فيلم معيّن كفيلم مفضّل.

دمج طلبات البحث في تطبيق الويب

  1. في MovieService (app/src/lib/MovieService.tsxأزِل التعليق عن عمليات الاستيراد التالية:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. استبدِل الدوالّ handleAddFavoritedMovie وhandleDeleteFavoritedMovie وhandleGetIfFavoritedMovie بالرمز البرمجي التالي:
// Add a movie to user's favorites
export const handleAddFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await addFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error adding movie to favorites:", error);
    throw error;
  }
};

// Remove a movie from user's favorites
export const handleDeleteFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await deleteFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error removing movie from favorites:", error);
    throw error;
  }
};

// Check if the movie is favorited by the user
export const handleGetIfFavoritedMovie = async (
  movieId: string
): Promise<boolean> => {
  try {
    const response = await getIfFavoritedMovie({ movieId });
    return !!response.data.favorite_movie;
  } catch (error) {
    console.error("Error checking if movie is favorited:", error);
    return false;
  }
};

الأفكار الرئيسية المستخلصة:

  • handleAddFavoritedMovie وhandleDeleteFavoritedMovie: استخدِم عمليات التحويل لإضافة فيلم إلى قائمة المستخدم المفضّلة أو إزالته منها بأمان.
  • handleGetIfFavoritedMovie: يستخدم طلب البحث getIfFavoritedMovie للتحقّق مما إذا كان المستخدم قد وضع علامة على فيلم على أنّه مفضّل.

أمثلة واقعية

يمكنك الآن إضافة الأفلام إلى قائمة الأفلام المفضّلة أو إزالتها منها من خلال النقر على رمز القلب في بطاقات الأفلام وصفحة تفاصيل الفيلم. بالإضافة إلى ذلك، يمكنك عرض أفلامك المفضّلة في صفحة ملفك الشخصي.

تنفيذ ميزة "مراجعات المستخدمين"

بعد ذلك، عليك تنفيذ قسم إدارة مراجعات المستخدمين في التطبيق.

تنفيذ الموصّلات

  1. في mutations.gql (dataconnect/movie-connector/mutations.gql): أضِف الطفرات التالية:
# Add a review for a movie
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
@auth(level: USER) {
  review_insert(
    data: {
      userId_expr: "auth.uid"
      movieId: $movieId
      rating: $rating
      reviewText: $reviewText
      reviewDate_date: { today: true }
    }
  )
}

# Delete a user's review for a movie
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
  review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

الأفكار الرئيسية المستخلصة:

  • userId_expr: "auth.uid": تضمن ربط المراجعات بالمستخدم الذي تمّت مصادقة هويته.
  • reviewDate_date: { today: true }: يتم إنشاء التاريخ الحالي للمراجعة تلقائيًا باستخدام DataConnect، ما يغني عن إدخال البيانات يدويًا.

دمج طلبات البحث في تطبيق الويب

  1. في MovieService (app/src/lib/MovieService.tsxأزِل التعليق عن عمليات الاستيراد التالية:
import { addReview, deleteReview } from "@movie/dataconnect";
  1. استبدِل الدالتَين handleAddReview وhandleDeleteReview بالرمز البرمجي التالي:
// Add a review to a movie
export const handleAddReview = async (
  movieId: string,
  rating: number,
  reviewText: string
): Promise<void> => {
  try {
    await addReview({ movieId, rating, reviewText });
  } catch (error) {
    console.error("Error adding review:", error);
    throw error;
  }
};

// Delete a review from a movie
export const handleDeleteReview = async (movieId: string): Promise<void> => {
  try {
    await deleteReview({ movieId });
  } catch (error) {
    console.error("Error deleting review:", error);
    throw error;
  }
};

الأفكار الرئيسية المستخلصة:

  • handleAddReview: تستدعي عملية التحويل addReview لإضافة مراجعة للفيلم المحدّد، مع ربطها بأمان بالمستخدم الذي تمّت مصادقته.
  • handleDeleteReview: يستخدم هذا الإجراء عملية التحويل deleteReview لإزالة مراجعة لفيلم من قِبل المستخدم الذي تمّت مصادقة هويته.

أمثلة واقعية

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

8- الفلاتر المتقدّمة ومطابقة النص الجزئي

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

ece70ee0ab964e28.png

تنفيذ الموصّلات

  1. افتح queries.gql في dataconnect/movie-connector/.
  2. أضِف الطلب التالي لتفعيل إمكانات البحث المختلفة:
# Search for movies, actors, and reviews
query SearchAll(
  $input: String
  $minYear: Int!
  $maxYear: Int!
  $minRating: Float!
  $maxRating: Float!
  $genre: String!
) @auth(level: PUBLIC) {
  moviesMatchingTitle: movies(
    where: {
      _and: [
        { releaseYear: { ge: $minYear } }
        { releaseYear: { le: $maxYear } }
        { rating: { ge: $minRating } }
        { rating: { le: $maxRating } }
        { genre: { contains: $genre } }
        { title: { contains: $input } }
      ]
    }
  ) {
    id
    title
    genre
    rating
    imageUrl
  }
  moviesMatchingDescription: movies(
    where: {
      _and: [
        { releaseYear: { ge: $minYear } }
        { releaseYear: { le: $maxYear } }
        { rating: { ge: $minRating } }
        { rating: { le: $maxRating } }
        { genre: { contains: $genre } }
        { description: { contains: $input } }
      ]
    }
  ) {
    id
    title
    genre
    rating
    imageUrl
  }
  actorsMatchingName: actors(where: { name: { contains: $input } }) {
    id
    name
    imageUrl
  }
  reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) {
    id
    rating
    reviewText
    reviewDate
    movie {
      id
      title
    }
    user {
      id
      username
    }
  }
}

الأفكار الرئيسية المستخلصة:

  • عامل التشغيل _and: يجمع بين شروط متعدّدة في طلب بحث واحد، ما يسمح بفلترة البحث حسب عدة حقول مثل releaseYear وrating وgenre.
  • عامل التشغيل contains: يبحث عن مطابقات نصية جزئية داخل الحقول. في طلب البحث هذا، يتم البحث عن المطابقات ضمن title أو description أو name أو reviewText.
  • عبارة where: تحدِّد شروط فلترة البيانات. يستخدم كل قسم (الأفلام والممثلون والمراجعات) عبارة where لتحديد المعايير المحدّدة للبحث.

دمج طلبات البحث في تطبيق الويب

  1. في MovieService (app/src/lib/MovieService.tsxأزِل التعليق عن عمليات الاستيراد التالية:
import { searchAll, SearchAllData } from "@movie/dataconnect";
  1. استبدِل الدالة handleSearchAll بالرمز البرمجي التالي:
// Function to perform the search using the query and filters
export const handleSearchAll = async (
  searchQuery: string,
  minYear: number,
  maxYear: number,
  minRating: number,
  maxRating: number,
  genre: string
): Promise<SearchAllData | null> => {
  try {
    const response = await searchAll({
      input: searchQuery,
      minYear,
      maxYear,
      minRating,
      maxRating,
      genre,
    });

    return response.data;
  } catch (error) {
    console.error("Error performing search:", error);
    return null;
  }
};

الأفكار الرئيسية المستخلصة:

  • handleSearchAll: تستخدِم هذه الدالة طلب البحث searchAll لإجراء بحث استنادًا إلى إدخال المستخدم، مع فلترة النتائج حسب مَعلمات مثل السنة والتقييم والنوع والمطابقات النصية الجزئية.

أمثلة واقعية

انتقِل إلى صفحة "البحث المتقدّم" من شريط التنقل في تطبيق الويب. يمكنك الآن البحث عن الأفلام والممثلين والمراجعات باستخدام فلاتر ومدخلات مختلفة، ما يؤدي إلى الحصول على نتائج بحث مفصّلة ومخصّصة.

9- اختياري: النشر على Cloud (يجب تفعيل الفوترة)

بعد الانتهاء من دورة التطوير على الجهاز، حان وقت نشر المخطّط والبيانات والاستعلامات على الخادم. ويمكن إجراء ذلك باستخدام إضافة Firebase Data Connect في VS Code أو واجهة Firebase CLI.

إضافة تطبيق ويب في وحدة تحكّم Firebase

  1. أنشئ تطبيق ويب في وحدة تحكّم Firebase وسجِّل رقم تعريف تطبيقك.

7030822793e4d75b.png

  1. إعداد تطبيق ويب في وحدة تحكّم Firebase من خلال النقر على "إضافة تطبيق" يمكنك تجاهل عملية إعداد حزمة SDK وضبطها في الوقت الحالي، ولكن عليك تدوين عنصر firebaseConfig الذي تم إنشاؤه.
  2. استبدِل firebaseConfig في app/src/lib/firebase.tsx:
const firebaseConfig = {
  apiKey: "API_KEY",
  authDomain: "PROJECT_ID.firebaseapp.com",
  projectId: "PROJECT_ID",
  storageBucket: "PROJECT_ID.appspot.com",
  messagingSenderId: "SENDER_ID",
  appId: "APP_ID"
};
  1. إنشاء تطبيق الويب: في مجلد app، استخدِم Vite لإنشاء تطبيق الويب من أجل استضافة عملية النشر:
cd app
npm run build

إعداد Firebase Authentication في وحدة التحكّم

  1. إعداد Firebase Auth باستخدام ميزة "تسجيل الدخول باستخدام حساب Google"

62af2f225e790ef6.png

  1. (اختياري) اسمح بالنطاقات لخدمة (Firebase Auth) [https://firebase.google.com/docs/auth/web/hosting] في وحدة تحكّم المشروع (مثل http://127.0.0.1):
  • في إعدادات Auth (المصادقة)، اختَر مشروعك وانتقِل إلى (النطاقات المعتمَدة) [https://firebase.google.com/docs/auth/web/hosting]. انقر على "إضافة نطاق" وأدرِج نطاقك المحلي في القائمة.

c255098f12549886.png

النشر باستخدام Firebase CLI

  1. في dataconnect/dataconnect.yaml، تأكَّد من أنّ معرّف المثيل وقاعدة البيانات ومعرّف الخدمة يتطابقان مع مشروعك:
specVersion: "v1alpha"
serviceId: "your-service-id"
location: "us-central1"
schema:
  source: "./schema"
  datasource:
    postgresql:
      database: "your-database-id"
      cloudSql:
        instanceId: "your-instance-id"
connectorDirs: ["./movie-connector"]
  1. تأكَّد من إعداد واجهة Firebase CLI مع مشروعك.
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. في الوحدة الطرفية، نفِّذ الأمر التالي للنشر:
firebase deploy --only dataconnect,hosting
  1. نفِّذ هذا الأمر لمقارنة تغييرات المخطّط:
firebase dataconnect:sql:diff
  1. إذا كانت التغييرات مقبولة، طبِّقها باستخدام:
firebase dataconnect:sql:migrate

سيتم تعديل آلة Cloud SQL لنظام PostgreSQL الافتراضية باستخدام المخطّط والبيانات النهائيَين اللذَين تم نشرهما. يمكنك مراقبة الحالة في "وحدة تحكّم Firebase".

من المفترض أن يظهر تطبيقك الآن على الرابط your-project.web.app/. بالإضافة إلى ذلك، يمكنك النقر على تشغيل (الإصدار العلني) في لوحة Firebase Data Connect، تمامًا كما فعلت مع المحاكيات المحلية، لإضافة بيانات إلى بيئة الإصدار العلني.

10- اختياري: البحث المرئي باستخدام Firebase Data Connect

في هذا القسم، ستفعّل ميزة البحث بالاستناد إلى المتجهات في تطبيق مراجعات الأفلام باستخدام Firebase Data Connect. تتيح هذه الميزة إجراء عمليات بحث مستندة إلى المحتوى، مثل العثور على أفلام تتضمّن أوصافًا مشابهة باستخدام عمليات إدراج المتجهات.

4b5aca5a447d2feb.png

تعديل المخطّط لتضمين عمليات التضمين لحقل معيّن

  1. في dataconnect/schema/schema.gql، أضِف حقل descriptionEmbedding إلى جدول Movie:
type Movie
  # The below parameter values are generated by default with @table, and can be edited manually.
  @table {
  # implicitly calls @col to generates a column name. ex: @col(name: "movie_id")
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
  descriptionEmbedding: Vector @col(size:768) # Enables vector search
}

الفكرة الرئيسية المستخلصة:

  • descriptionEmbedding: Vector @col(size:768): يخزّن هذا الحقل عمليات التضمين الدلالية لأوصاف الأفلام، ما يتيح البحث عن المحتوى المستنِد إلى المتجهات في تطبيقك.

تفعيل Vertex AI

  1. اتّبِع دليل المتطلبات الأساسية لإعداد واجهات برمجة تطبيقات Vertex AI باستخدام Google Cloud. هذه الخطوة ضرورية لتفعيل وظائف إنشاء التضمين والبحث عن المتجهات.
  2. أعِد نشر المخطّط لتفعيل pgvector والبحث بالاستناد إلى المتجهات من خلال النقر على "النشر في قناة الإصدار العلني" باستخدام إضافة Firebase Data Connect في VSCode.

ملء قاعدة البيانات بالعناصر المضمّنة

  1. افتح مجلد dataconnect في VSCode وانقر على Run(local) (تشغيل (محلي)) في optional_vector_embed.gql لملء قاعدة بياناتك بإدراج الأفلام.

b858da780f6ec103.png

إضافة طلب بحث متجه

  1. في dataconnect/movie-connector/queries.gql، أضِف الطلب التالي لإجراء عمليات بحث متجه:
# Search movie descriptions using L2 similarity with Vertex AI
query SearchMovieDescriptionUsingL2Similarity($query: String!)
@auth(level: PUBLIC) {
  movies_descriptionEmbedding_similarity(
    compare_embed: { model: "textembedding-gecko@003", text: $query }
    method: L2
    within: 2
    limit: 5
  ) {
    id
    title
    description
    tags
    rating
    imageUrl
  }
}

الأفكار الرئيسية المستخلصة:

  • compare_embed: لتحديد نموذج التضمين (textembedding-gecko@003) والنص الذي يتم إدخاله ($query) للمقارنة.
  • method: تحدّد طريقة التشابه (L2)، التي تمثّل المسافة الإقليدية.
  • within: يحدّ هذا الخيار من البحث ليشمل الأفلام التي تبلغ المسافة بين محتوى كل فيلم ومحتوى الفيلم المُدرَج في قاعدة البيانات L2‏ 2 أو أقل، مع التركيز على الأفلام التي تتطابق مع المحتوى المُدرَج في قاعدة البيانات بشكل كبير.
  • limit: تقييد عدد النتائج المعروضة بـ 5

تنفيذ وظيفة البحث بالاستناد إلى المتجهات في التطبيق

  1. في app/src/lib/MovieService.ts، أزِل التعليقات من عمليات الاستيراد التالية:

تنفيذ وظيفة البحث عن المتجهات في التطبيق

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

في app/src/lib/ MovieService.ts، أزِل التعليق من عمليات الاستيراد التالية من حِزم SDK، وسيعمل هذا الأمر مثل أي طلب بحث آخر.

import {
  searchMovieDescriptionUsingL2similarity,
  SearchMovieDescriptionUsingL2similarityData,
} from "@movie/dataconnect";

أضِف الدالة التالية لدمج البحث المستند إلى المتجهات في التطبيق:

// Perform vector-based search for movies based on description
export const searchMoviesByDescription = async (
  query: string
): Promise<
  | SearchMovieDescriptionUsingL2similarityData["movies_descriptionEmbedding_similarity"]
  | null
> => {
  try {
    const response = await searchMovieDescriptionUsingL2similarity({ query });
    return response.data.movies_descriptionEmbedding_similarity;
  } catch (error) {
    console.error("Error fetching movie descriptions:", error);
    return null;
  }
};


الأفكار الرئيسية المستخلصة:

  • searchMoviesByDescription: تستدعي هذه الدالة طلب البحث searchMovieDescriptionUsingL2similarity، مع تمرير نص الإدخال لإجراء بحث عن المحتوى بالاستناد إلى المتجهات.

أمثلة واقعية

انتقِل إلى قسم "بحث متجهات" في شريط التنقل واكتب عبارات مثل "رومانسي وحديث". ستظهر لك قائمة بالأفلام التي تتطابق مع المحتوى الذي تبحث عنه، أو يمكنك الانتقال إلى صفحة تفاصيل أي فيلم والاطّلاع على قسم الأفلام المشابهة في أسفل الصفحة.

7b71f1c75633c1be.png

11- الخاتمة

تهانينا، من المفترض أن يكون بإمكانك استخدام تطبيق الويب. إذا كنت تريد استخدام بيانات الأفلام الخاصة بك، لا داعي للقلق، يمكنك إدراج بياناتك باستخدام إضافة FDC عن طريق تقليد ملفات ‎_insert.gql أو إضافتها من خلال لوحة تنفيذ "ربط البيانات".

مزيد من المعلومات