1. قبل البدء
في هذا الدرس العملي، ستدمج 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
- كتابة واختبار طلبات بحث وعمليات تغيير مختلفة لتطبيق مراجعة أفلام
- تعرَّف على كيفية إنشاء حزمة تطوير البرامج (SDK) واستخدامها في التطبيق من خلال ميزة "ربط البيانات في Firebase".
- نشر المخطط وإدارة قاعدة البيانات بكفاءة
المتطلبات
- Git
- Visual Studio Code
- ثبِّت Node.js باستخدام nvm-windows (في نظام التشغيل Windows) أو nvm (في نظام التشغيل macOS أو Linux).
- إذا لم يسبق لك إنشاء مشروع على Firebase، أنشئ مشروعًا في وحدة تحكّم Firebase.
- (اختياري) للبحث المتّجه، يمكنك ترقية مشروعك إلى خطة أسعار Blaze للدفع حسب الاستخدام
2. إعداد بيئة التطوير
ستساعدك هذه المرحلة من الدرس العملي في إعداد البيئة لبدء إنشاء تطبيق مراجعة الأفلام باستخدام Firebase Data Connect.
- أنشئ نسخة طبق الأصل من مستودع المشروع وثبِّت التبعيات المطلوبة:
git clone https://github.com/firebaseextended/codelab-dataconnect-web cd codelab-dataconnect-web cd ./app && npm i npm run dev
- بعد تنفيذ هذه الأوامر، افتح http://localhost:5173 في المتصفّح للاطّلاع على تطبيق الويب الذي يتم تشغيله محليًا. سيكون هذا بمثابة الواجهة الأمامية لإنشاء تطبيق مراجعة الأفلام والتفاعل مع ميزاته.
- افتح مجلد
codelab-dataconnect-web
المستنسخ باستخدام Visual Studio Code. هذا هو المكان الذي ستحدّد فيه المخطط وتكتب طلبات البحث وتختبر وظائف التطبيق. - لاستخدام ميزات Data Connect، ثبِّت إضافة Firebase Data Connect في Visual Studio.
بدلاً من ذلك، يمكنك تثبيت الإضافة من Visual Studio Code Marketplace أو البحث عنها في VS Code. - افتح مشروعًا جديدًا في Firebase أو أنشئ مشروعًا جديدًا في وحدة تحكّم Firebase.
- ربط مشروعك على Firebase بإضافة Firebase Data Connect VSCode. في الإضافة، اتّبِع الخطوات التالية:
- انقر على الزر تسجيل الدخول.
- انقر على ربط مشروع Firebase واختَر مشروعك على Firebase.
- ابدأ محاكيات Firebase باستخدام إضافة Firebase Data Connect في VS Code:
انقر على بدء المحاكيات، ثم تأكَّد من أنّ المحاكيات تعمل في الوحدة الطرفية.
3- مراجعة قاعدة الرموز البرمجية الأولية
في هذا القسم، سنتعرّف على المجالات الرئيسية لقاعدة الرموز الأولية للتطبيق. على الرغم من أنّ التطبيق لا يتضمّن بعض الوظائف، إلا أنّه مفيد لفهم البنية العامة.
بنية المجلدات والملفات
تقدّم الأقسام الفرعية التالية نظرة عامة على بنية المجلدات والملفات في التطبيق.
دليل 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 في مشروعك على Firebase-
lib/dataconnect-sdk/
: يحتوي على حزمة SDK التي تم إنشاؤها. يمكنك تعديل موقع إنشاء حزمة SDK في ملفconnector/connector.yaml
وسيتم إنشاء حِزم SDK تلقائيًا في أي وقت تحدّد فيه طلب بحث أو تغييرًا.
4. تحديد مخطط لمراجعات الأفلام
في هذا القسم، ستحدّد بنية الكيانات الرئيسية في تطبيق الأفلام والعلاقات بينها في مخطط. يتم ربط الكيانات، مثل Movie
وUser
وActor
وReview
، بجداول قواعد البيانات، مع إنشاء علاقات باستخدام Firebase Data Connect وتوجيهات مخطط GraphQL. بعد إعداد هذه الميزة، سيصبح تطبيقك جاهزًا للتعامل مع كل شيء، بدءًا من البحث عن الأفلام الأعلى تقييمًا والفلترة حسب النوع، وصولاً إلى السماح للمستخدمين بترك مراجعات أو وضع علامة على الأفلام المفضّلة أو استكشاف أفلام مشابهة أو العثور على أفلام مقترَحة استنادًا إلى إدخال نصي من خلال البحث المتّجه.
العناصر والعلاقات الأساسية
يحتوي النوع Movie
على تفاصيل أساسية، مثل العنوان والنوع والعلامات، التي يستخدمها التطبيق في عمليات البحث وملفات الأفلام. يتتبّع النوع User
تفاعلات المستخدمين، مثل المراجعات وقوائم المفضّلة. Reviews
ربط المستخدمين بالأفلام، ما يتيح للتطبيق عرض التقييمات والملاحظات التي ينشئها المستخدمون
تساهم العلاقات بين الأفلام والممثلين والمستخدمين في جعل التطبيق أكثر ديناميكية. يساعد جدول الربط MovieActor
في عرض تفاصيل فريق التمثيل وأعمال الممثلين السينمائية. يتيح النوع FavoriteMovie
للمستخدمين إضافة الأفلام إلى قائمة المفضّلة، ما يسمح للتطبيق بعرض قائمة مفضّلة مخصّصة وإبراز الأفلام الرائجة.
إعداد جدول Movie
يحدّد النوع 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: معرّف فريد لكل فيلم، يتم إنشاؤه باستخدام
@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
، ما يؤدي إلى إنشاء علاقة مفتاح خارجي.
إعداد جدول Actor
انسخ مقتطف الرمز وألصِقه في ملف dataconnect/schema/schema.gql
:
type Actor @table {
id: UUID!
imageUrl: String!
name: String! @col(name: "name", dataType: "varchar(30)")
}
يمثّل النوع Actor
ممثلاً في قاعدة بيانات الأفلام، حيث يمكن أن يكون كل ممثل جزءًا من أفلام متعددة، ما يشكّل علاقة متعددة إلى متعددة.
إعداد جدول MovieActor
انسخ مقتطف الرمز وألصِقه في ملف 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"
}
الأفكار الرئيسية:
- الفيلم: يشير إلى نوع الفيلم، ويُنشئ ضمنيًا مفتاحًا خارجيًا movieId: UUID!.
- actor: يشير إلى نوع "المُنفِّذ"، ويُنشئ ضمنيًا مفتاحًا خارجيًا actorId: UUID!.
- role: تحدّد دور الممثل في الفيلم (مثلاً "main" أو "supporting").
إعداد جدول User
يحدّد النوع User
كيان مستخدم يتفاعل مع الأفلام من خلال ترك مراجعات أو إضافة الأفلام إلى المفضّلة.
انسخ مقتطف الرمز وألصِقه في ملف dataconnect/schema/schema.gql
:
type User
@table {
id: String! @col
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!
}
الأفكار الرئيسية:
- الفيلم: يشير إلى نوع الفيلم، وينشئ ضمنيًا مفتاحًا خارجيًا
movieId: UUID!
. - user: يشير إلى نوع المستخدم، وينشئ ضمنيًا مفتاحًا خارجيًا
userId: UUID!
.
إعداد جدول Review
يمثّل النوع Review
كيان المراجعة ويربط النوعَين User
وMovie
في علاقة متعددة إلى متعددة (يمكن لمستخدم واحد ترك العديد من المراجعات، ويمكن أن يتضمّن كل فيلم العديد من المراجعات).
انسخ مقتطف الرمز وألصِقه في ملف 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")
}
الأفكار الرئيسية:
- المستخدم: يشير إلى المستخدم الذي أضاف المراجعة.
- movie: تشير إلى الفيلم الذي تتم مراجعته.
- reviewDate: يتم ضبطها تلقائيًا على الوقت الذي تم فيه إنشاء المراجعة باستخدام
@default(expr: "request.time")
.
الحقول والقيم التلقائية التي يتم إنشاؤها تلقائيًا
يستخدم المخطط تعابير مثل @default(expr: "uuidV4()")
لإنشاء معرّفات فريدة وطوابع زمنية تلقائيًا. على سبيل المثال، يتم تلقائيًا ملء الحقل id
في النوعَين Movie
وReview
بمعرّف فريد عالمي (UUID) عند إنشاء سجلّ جديد.
بعد تحديد المخطط، سيصبح تطبيق الأفلام الخاص بك مزوّدًا بأساس متين لبنية البيانات والعلاقات.
5- استرداد أهم وأحدث الأفلام
في هذا القسم، ستُدرج بيانات أفلام تجريبية في المحاكيات المحلية، ثم ستنفّذ أدوات الربط (طلبات البحث) ورمز TypeScript لاستدعاء أدوات الربط هذه في تطبيق الويب. في النهاية، سيتمكّن تطبيقك من استرداد وعرض الأفلام الأعلى تقييمًا والأحدث مباشرةً من قاعدة البيانات.
إدراج بيانات وهمية للأفلام والممثلين والمراجعات
- في VSCode، افتح
dataconnect/moviedata_insert.gql
. تأكَّد من أنّ المحاكيات في إضافة Firebase Data Connect تعمل. - من المفترض أن يظهر لك الزر تشغيل (محلي) في أعلى الملف. انقر على هذا الزر لإدراج بيانات الأفلام التجريبية في قاعدة البيانات.
- راجِع نافذة تنفيذ Data Connect للتأكّد من أنّه تمت إضافة البيانات بنجاح.
تنفيذ الموصّل
- فتح
dataconnect/movie-connector/queries.gql
ستجد استعلامًا أساسيًا فيListMovies
في التعليقات: يجلب هذا الطلب جميع الأفلام وتفاصيلها (مثلquery ListMovies @auth(level: PUBLIC) { movies { id title imageUrl releaseYear genre rating tags description } }
id
وtitle
وreleaseYear
)، ولكنّه لا يرتب الأفلام. - استبدِل طلب البحث
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 } }
- انقر على الزر تشغيل (محلي) لتنفيذ طلب البحث في قاعدة البيانات المحلية. يمكنك أيضًا إدخال متغيّرات طلب البحث في لوحة الإعداد قبل التشغيل.
الأفكار الرئيسية:
-
movies()
: حقل طلب بحث GraphQL لاسترداد بيانات الأفلام من قاعدة البيانات orderByRating
: مَعلمة لترتيب الأفلام حسب التقييم (تصاعديًا أو تنازليًا).- استبدِل
orderByReleaseYear
بما يلي:مَعلمة لترتيب الأفلام حسب سنة الإصدار (تصاعديًا أو تنازليًا). - استبدِل
limit
بـ: تقيّد هذه السمة عدد الأفلام التي يتم عرضها.
دمج طلبات البحث في تطبيق الويب
في هذا الجزء من الدرس العملي، ستستخدم طلبات البحث المحدّدة في القسم السابق في تطبيق الويب. وتنشئ محاكيات Firebase Data Connect حِزم SDK استنادًا إلى المعلومات الواردة في ملفات .gql
(تحديدًا schema.gql
وqueries.gql
وmutations.gql
) وملف connector.yaml
. ويمكن استدعاء حِزم SDK هذه مباشرةً في تطبيقك.
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق من عبارة الاستيراد في أعلى الصفحة: الدالةimport { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
listMovies
ونوع الاستجابةListMoviesData
والقيمة الثابتةOrderDirection
هي جميعها حِزم تطوير برامج (SDK) أنشأتها محاكيات Firebase Data Connect استنادًا إلى المخطط والاستعلامات التي حدّدتها سابقًا . - استبدِل الدالتَين
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 أفلام وأحدثها على الصفحة الرئيسية للتطبيق.
مثال عملي
أعِد تحميل تطبيق الويب للاطّلاع على طلب البحث أثناء التنفيذ. تعرض الصفحة الرئيسية الآن قائمة الأفلام بشكل ديناميكي، حيث يتم استرداد البيانات مباشرةً من قاعدة البيانات المحلية. ستظهر لك الأفلام الأعلى تقييمًا والأحدث بسلاسة، ما يعكس البيانات التي أعددتها للتو.
6. عرض تفاصيل الأفلام والممثلين
في هذا القسم، ستنفّذ وظيفة استرداد معلومات تفصيلية عن فيلم أو ممثل باستخدام المعرّفات الفريدة الخاصة بهما. ولا يقتصر ذلك على استرداد البيانات من الجداول الخاصة بها، بل يشمل أيضًا ربط الجداول ذات الصلة لعرض تفاصيل شاملة، مثل مراجعات الأفلام وأعمال الممثلين السينمائية.
تنفيذ الموصلات
- افتح
dataconnect/movie-connector/queries.gql
في مشروعك. - أضِف طلبات البحث التالية لاسترداد تفاصيل الأفلام والممثلين:
# 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 } } }
- احفظ التغييرات وراجِع طلبات البحث.
الأفكار الرئيسية:
-
movie()
/actor()
: حقول طلب بحث GraphQL لاسترداد فيلم أو ممثل واحد من الجدولMovies
أوActors
-
_on_
: يتيح ذلك الوصول المباشر إلى الحقول من نوع مرتبط يتضمّن علاقة مفتاح خارجي. على سبيل المثال، يؤدي طلب البحثreviews_on_movie
إلى جلب جميع المراجعات المرتبطة بفيلم معيّن. _via_
: تُستخدَم للتنقّل بين علاقات متعددة إلى متعددة من خلال جدول ربط. على سبيل المثال، يصلactors_via_MovieActor
إلى النوعActor
من خلال جدول الربطMovieActor
، ويتم فلترة الممثلين استنادًا إلى دورهم (على سبيل المثال، "رئيسي" أو "ثانوي") باستخدام الشرطwhere
.
اختبار طلب البحث عن طريق إدخال بيانات وهمية
- في لوحة تنفيذ Data Connect، يمكنك اختبار طلب البحث عن طريق إدخال معرّفات وهمية، مثل:
{"id": "550e8400-e29b-41d4-a716-446655440000"}
- انقر على تشغيل (محلي) لـ
GetMovieById
لاسترداد التفاصيل حول "Quantum Paradox" (الفيلم التجريبي الذي يرتبط به رقم التعريف أعلاه).
دمج طلبات البحث في تطبيق الويب
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق عن عمليات الاستيراد التالية:import { getMovieById, GetMovieByIdData } from "@movie/dataconnect"; import { GetActorByIdData, getActorById } from "@movie/dataconnect";
- استبدِل الدالتَين
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
: هذان النوعان من النتائج يُستخدمان لعرض تفاصيل الأفلام والممثلين في التطبيق.
مثال عملي
الآن، انتقِل إلى الصفحة الرئيسية لتطبيق الويب. انقر على فيلم، وستتمكّن من الاطّلاع على جميع تفاصيله، بما في ذلك الممثلون والمراجعات، وهي معلومات يتم استخلاصها من جداول ذات صلة. وبالمثل، سيؤدي النقر على ممثل إلى عرض الأفلام التي شارك فيها.
7. التعامل مع مصادقة المستخدم
في هذا القسم، ستنفّذ وظائف تسجيل الدخول والخروج للمستخدمين باستخدام خدمة "المصادقة" من Firebase. ستستخدم أيضًا بيانات Firebase Authentication لاسترداد بيانات المستخدم أو إدراجها أو تعديلها مباشرةً في Firebase DataConnect، ما يضمن إدارة المستخدمين بشكل آمن داخل تطبيقك.
تنفيذ الموصلات
- افتح
mutations.gql
فيdataconnect/movie-connector/
. - أضِف عملية التعديل التالية لإنشاء المستخدم الحالي الذي تمّت مصادقته أو تعديله:
# 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
: يجلب جميع المراجعات المرتبطة بالمستخدم، بما في ذلكid
وtitle
الخاصان بالفيلم. favorite_movies_on_user
: تسترد هذه السمة جميع الأفلام التي وضع المستخدم علامة عليها كمفضّلة، بما في ذلك معلومات تفصيلية مثلgenre
وreleaseYear
وrating
وmetadata
.
-
دمج طلبات البحث في تطبيق الويب
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق من أمام عمليات الاستيراد التالية:import { upsertUser } from "@movie/dataconnect"; import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
- استبدِل الدالتَين
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". بعد تسجيل الدخول، انقر على "ملفي الشخصي". سيكون هذا الحقل فارغًا في الوقت الحالي، ولكنّك أعددت الأساس لمعالجة البيانات الخاصة بالمستخدمين في تطبيقك.
8. تنفيذ تفاعلات المستخدمين
في هذا القسم من الدرس العملي، ستنفّذ تفاعلات المستخدمين في تطبيق مراجعات الأفلام، وتحديدًا ستتيح للمستخدمين إدارة أفلامهم المفضّلة وكتابة المراجعات أو حذفها.
السماح للمستخدم بإضافة فيلم إلى قائمة الأفلام المفضّلة
في هذا القسم، ستُعدّ قاعدة البيانات للسماح للمستخدمين بإضافة فيلم إلى قائمة الأفلام المفضّلة.
تنفيذ الموصلات
- افتح
mutations.gql
فيdataconnect/movie-connector/
. - أضِف عمليات التغيير التالية للتعامل مع إضافة الأفلام إلى قائمة المفضّلة:
# 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" مباشرةً، ما يضمن عدم الوصول إلى بيانات المستخدم الذي تمّت مصادقته أو تعديلها إلا من خلاله.
التحقّق ممّا إذا كان الفيلم قد تمّت إضافته إلى قائمة الأفلام المفضّلة
- افتح
queries.gql
فيdataconnect/movie-connector/
. - أضِف طلب البحث التالي لمعرفة ما إذا كان فيلم قد تم وضعه في قائمة الأفلام المفضّلة:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) { movieId } }
الأفكار الرئيسية:
auth.uid
: تضمن الوصول الآمن إلى البيانات الخاصة بالمستخدمين باستخدام "مصادقة Firebase".-
favorite_movie
: يتحقّق من جدول الربطfavorite_movies
لمعرفة ما إذا كان المستخدم الحالي قد وضع علامة على فيلم معيّن كفيلم مفضّل.
دمج طلبات البحث في تطبيق الويب
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق عن عمليات الاستيراد التالية:import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
- استبدِل الدوال
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
للتحقّق مما إذا كان المستخدم قد وضع علامة "مفضّلة" على فيلم.
مثال عملي
يمكنك الآن إضافة أفلام إلى قائمة الأفلام المفضّلة أو إزالتها منها من خلال النقر على رمز القلب في بطاقات الأفلام وصفحة تفاصيل الفيلم. بالإضافة إلى ذلك، يمكنك الاطّلاع على أفلامك المفضّلة في صفحة ملفك الشخصي.
السماح للمستخدمين بإضافة مراجعات أو حذفها
بعد ذلك، عليك تنفيذ القسم الخاص بإدارة مراجعات المستخدمين في التطبيق.
تنفيذ الموصلات
في 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، ما يلغي الحاجة إلى إدخاله يدويًا.
دمج طلبات البحث في تطبيق الويب
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق عن عمليات الاستيراد التالية:import { addReview, deleteReview } from "@movie/dataconnect";
- استبدِل الدالتَين
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
لإزالة مراجعة فيلم كتبها المستخدم الذي تمّت مصادقته.
مثال عملي
يمكن للمستخدمين الآن كتابة مراجعات للأفلام على صفحة تفاصيل الفيلم. ويمكنهم أيضًا الاطّلاع على مراجعاتهم وحذفها من صفحة ملفهم الشخصي، ما يمنحهم تحكّمًا كاملاً في تفاعلاتهم مع التطبيق.
9. الفلاتر المتقدّمة ومطابقة النص الجزئي
في هذا القسم، ستنفّذ إمكانات بحث متقدّمة، ما يتيح للمستخدمين البحث عن الأفلام استنادًا إلى مجموعة من التقييمات وسنوات الإصدار، والفلترة حسب الأنواع والعلامات، وإجراء مطابقة جزئية للنص في العناوين أو الأوصاف، وحتى الجمع بين فلاتر متعددة للحصول على نتائج أكثر دقة.
تنفيذ الموصلات
- افتح
queries.gql
فيdataconnect/movie-connector/
. - أضِف طلب البحث التالي لإتاحة إمكانات بحث متنوعة:
# 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
لتحديد المعايير المحدّدة للبحث.
دمج طلبات البحث في تطبيق الويب
- في
MovieService
(app/src/lib/MovieService.tsx
)، أزِل التعليق عن عمليات الاستيراد التالية:import { searchAll, SearchAllData } from "@movie/dataconnect";
- استبدِل الدالة
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
لإجراء بحث استنادًا إلى إدخال المستخدم، مع فلترة النتائج حسب مَعلمات مثل السنة والتقييم والنوع والمطابقات الجزئية للنص.
مثال عملي
انتقِل إلى صفحة "البحث المتقدّم" من شريط التنقّل في تطبيق الويب. يمكنك الآن البحث عن الأفلام والممثلين والمراجعات باستخدام فلاتر ومدخلات مختلفة، والحصول على نتائج بحث مفصّلة ومخصّصة.
10. اختياري: النشر على السحابة الإلكترونية (يجب توفّر الفوترة)
بعد الانتهاء من عملية التطوير المحلية، حان الوقت لنشر المخطط والبيانات والاستعلامات على الخادم. يمكن إجراء ذلك باستخدام إضافة Firebase Data Connect VS Code أو واجهة سطر الأوامر (CLI) في Firebase.
ترقية خطة أسعار Firebase
لدمج Firebase Data Connect مع Cloud SQL for PostgreSQL، يجب أن يكون مشروعك على Firebase ضمن خطة الأسعار "الدفع حسب الاستخدام" (Blaze)، ما يعني أنّه مرتبط بحساب فوترة على السحابة الإلكترونية.
- يتطلّب حساب الفوترة في Cloud طريقة دفع، مثل بطاقة الائتمان.
- إذا كنت حديث العهد باستخدام Firebase وGoogle Cloud، تحقَّق ممّا إذا كنت مؤهَّلاً للحصول على رصيد بقيمة 300 دولار أمريكي وحساب فوترة على Cloud في الفترة التجريبية المجانية.
- إذا كنت تجري هذا الدرس العملي المبرمَج كجزء من حدث، اسأل المنظِّم عمّا إذا كانت هناك أي أرصدة في Cloud متاحة.
لترقية مشروعك إلى خطة Blaze، اتّبِع الخطوات التالية:
- في "وحدة تحكّم Firebase"، اختَر ترقية خطتك.
- اختَر خطة Blaze. اتّبِع التعليمات الظاهرة على الشاشة لربط حساب فوترة على Cloud بمشروعك.
إذا احتجت إلى إنشاء حساب فوترة على Cloud كجزء من عملية الترقية هذه، قد تحتاج إلى الرجوع إلى مسار الترقية في وحدة تحكّم Firebase لإكمال عملية الترقية.
ربط تطبيق الويب بمشروعك على Firebase
- سجِّل تطبيق الويب في مشروعك على Firebase باستخدام وحدة تحكّم Firebase:
- افتح مشروعك، ثم انقر على إضافة تطبيق.
- تجاهَل إعداد حزمة تطوير البرامج (SDK) وإعدادات الضبط في الوقت الحالي، ولكن احرص على نسخ العنصر
firebaseConfig
الذي تم إنشاؤه.
- استبدِل
firebaseConfig
الحالي فيapp/src/lib/firebase.tsx
بالإعداد الذي نسخته للتو من وحدة تحكّم Firebase.const firebaseConfig = { apiKey: "API_KEY", authDomain: "PROJECT_ID.firebaseapp.com", projectId: "PROJECT_ID", storageBucket: "PROJECT_ID.firebasestorage.app", messagingSenderId: "SENDER_ID", appId: "APP_ID" };
- إنشاء تطبيق الويب: في VS Code، في المجلد
app
، استخدِم Vite لإنشاء تطبيق الويب من أجل نشر الاستضافة:cd app npm run build
إعداد خدمة "المصادقة عبر Firebase" في مشروعك على Firebase
- إعداد مصادقة Firebase باستخدام ميزة "تسجيل الدخول باستخدام حساب Google"
- (اختياري) السماح بالنطاقات في مصادقة Firebase باستخدام وحدة تحكّم Firebase (على سبيل المثال،
http://127.0.0.1
).- في إعدادات المصادقة، انتقِل إلى النطاقات المعتمَدة.
- انقر على "إضافة نطاق" وأدرِج نطاقك المحلي في القائمة.
النشر باستخدام Firebase CLI
- في
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"]
- تأكَّد من إعداد واجهة سطر الأوامر (CLI) في Firebase مع مشروعك:
npm i -g firebase-tools firebase login --reauth firebase use --add
- في الوحدة الطرفية، شغِّل الأمر التالي لنشر التطبيق:
firebase deploy --only dataconnect,hosting
- نفِّذ الأمر التالي لمقارنة تغييرات المخطط:
firebase dataconnect:sql:diff
- إذا كانت التغييرات مقبولة، طبِّقها باستخدام:
firebase dataconnect:sql:migrate
سيتم تعديل آلة Cloud SQL الافتراضية التي تستخدم PostgreSQL باستخدام المخطط والبيانات النهائية التي تم نشرها. يمكنك تتبُّع الحالة في وحدة تحكُّم Firebase.
من المفترض أن تتمكّن الآن من مشاهدة تطبيقك مباشرةً على your-project.web.app/
. بالإضافة إلى ذلك، يمكنك النقر على تشغيل (الإنتاج) في لوحة Firebase Data Connect، تمامًا كما فعلت مع المحاكيات المحلية، لإضافة بيانات إلى بيئة الإنتاج.
11. اختياري: البحث المتّجه باستخدام Firebase Data Connect (يجب توفّر معلومات الفوترة)
في هذا القسم، ستفعِّل البحث المتّجه في تطبيق مراجعات الأفلام باستخدام Firebase Data Connect. تتيح هذه الميزة إجراء عمليات بحث مستندة إلى المحتوى، مثل العثور على أفلام تتضمّن أوصافًا مشابهة باستخدام التضمينات المتجهة.
تتطلّب هذه الخطوة أن تكون قد أكملت الخطوة الأخيرة من هذا الدرس العملي لتنفيذ عملية النشر على Google Cloud.
تعديل المخطّط ليشمل عمليات تضمين لحقل معيّن
في 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
- اتّبِع دليل المتطلبات الأساسية لإعداد واجهات Vertex AI API من Google Cloud. هذه الخطوة ضرورية لإتاحة وظيفتَي إنشاء عمليات التضمين والبحث المتّجه.
- أعِد نشر المخطط لتفعيل
pgvector
والبحث المتّجه من خلال النقر على "النشر في مرحلة الإنتاج" باستخدام إضافة Firebase Data Connect VS Code.
تعبئة قاعدة البيانات بالتضمينات
- افتح مجلد
dataconnect
في VS Code. - انقر على تشغيل(محلي) في
optional_vector_embed.gql
لملء قاعدة البيانات بعمليات التضمين الخاصة بالأفلام.
إضافة طلب بحث عن تطابق دلالي
في 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.
تنفيذ وظيفة البحث الدلالي في تطبيقك
بعد إعداد المخطط وطلب البحث، يمكنك دمج البحث المتّجه في طبقة الخدمة في تطبيقك. تتيح لك هذه الخطوة استدعاء طلب البحث من تطبيق الويب.
- في
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
، وتمرّر النص المُدخَل لإجراء بحث عن المحتوى المستند إلى المتجهات.
مثال عملي
انتقِل إلى قسم "البحث المتّجه" في شريط التنقّل واكتب عبارات مثل "رومانسي وعصري". ستظهر لك قائمة بالأفلام التي تتطابق مع المحتوى الذي تبحث عنه، أو يمكنك الانتقال إلى صفحة تفاصيل أي فيلم والاطّلاع على قسم "أفلام مشابهة" في أسفل الصفحة.
12. الخاتمة
تهانينا، من المفترض أن تتمكّن من استخدام تطبيق الويب. إذا كنت تريد تجربة بيانات الأفلام الخاصة بك، لا تقلق، يمكنك إدراج بياناتك باستخدام إضافة Firebase Data Connect عن طريق محاكاة ملفات _insert.gql
، أو إضافتها من خلال لوحة تنفيذ Data Connect في VS Code.