إنشاء تطبيق لتنبيه أسعار الأسهم باستخدام React و Apollo GraphQL و Hasura

نشرت: 2022-03-10
ملخص سريع ↬ في هذه المقالة ، سوف نتعلم كيفية إنشاء تطبيق قائم على الحدث وإرسال إشعار عبر الويب عند تشغيل حدث معين. سنقوم بإعداد جداول قاعدة البيانات والأحداث والمشغلات المجدولة على محرك Hasura GraphQL ونربط نقطة نهاية GraphQL بتطبيق الواجهة الأمامية لتسجيل تفضيل سعر السهم للمستخدم.

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

ما مدى روعة ذلك إذا تمكنت من الحصول على تحديثات سعر الأسهم المفضلة لديك على هاتفك؟

في هذه المقالة ، سننشئ تطبيق تنبيه أسعار الأسهم باستخدام محرك React و Apollo GraphQL و Hasura GraphQL. سنبدأ المشروع من كود create-react-app معياري وسنقوم ببناء كل شيء على الأرض. سنتعلم كيفية إعداد جداول قاعدة البيانات والأحداث على وحدة تحكم Hasura. سنتعلم أيضًا كيفية توصيل أحداث Hasura للحصول على تحديثات أسعار الأسهم باستخدام إشعارات الدفع عبر الويب.

إليك نظرة سريعة على ما سنبنيه:

نظرة عامة على تطبيق منبه أسعار الأسهم
تطبيق إشعار سعر السهم

هيا بنا نذهب!

المزيد بعد القفز! أكمل القراءة أدناه ↓

نظرة عامة حول ماهية هذا المشروع

سيتم تخزين بيانات الأسهم (بما في ذلك المقاييس مثل عالية ، ومنخفضة ، ومفتوحة ، وإغلاق ، والحجم ) في قاعدة بيانات Postgres المدعومة من Hasura. سيكون المستخدم قادرًا على الاشتراك في سهم معين بناءً على بعض القيمة أو يمكنه اختيار تلقي إشعار كل ساعة. سيتلقى المستخدم إشعارًا عبر الويب بمجرد استيفاء معايير الاشتراك الخاصة به.

يبدو هذا وكأنه الكثير من الأشياء ومن الواضح أنه سيكون هناك بعض الأسئلة المفتوحة حول كيفية بناء هذه القطع.

إليك خطة حول كيفية إنجاز هذا المشروع في أربع خطوات:

  1. إحضار بيانات المخزونات باستخدام برنامج نصي NodeJs
    سنبدأ بجلب بيانات المخزون باستخدام برنامج نصي بسيط NodeJs من أحد مزودي واجهة برمجة تطبيقات الأسهم - Alpha Vantage. سيقوم هذا البرنامج النصي بجلب البيانات لمخزون معين في فترات زمنية تبلغ 5 دقائق. تتضمن استجابة API عالية ومنخفضة وفتح وإغلاق وحجم . سيتم بعد ذلك إدراج هذه البيانات في قاعدة بيانات Postgres المدمجة مع الواجهة الخلفية Hasura.
  2. إعداد محرك Hasura GraphQL
    سنقوم بعد ذلك بإعداد بعض الجداول في قاعدة بيانات Postgres لتسجيل نقاط البيانات. تنشئ Hasura تلقائيًا مخططات GraphQL والاستعلامات والطفرات لهذه الجداول.
  3. الواجهة الأمامية باستخدام React و Apollo Client
    تتمثل الخطوة التالية في دمج طبقة GraphQL باستخدام عميل Apollo وموفر Apollo (نقطة نهاية GraphQL المقدمة من Hasura). سيتم عرض نقاط البيانات كمخططات على الواجهة الأمامية. سنقوم أيضًا ببناء خيارات الاشتراك وسنطلق الطفرات المقابلة على طبقة GraphQL.
  4. إعداد مشغلات الحدث / المجدولة
    يوفر Hasura أدوات ممتازة حول المشغلات. سنضيف الأحداث والمشغلات المجدولة في جدول بيانات الأسهم. سيتم تعيين هذه المشغلات إذا كان المستخدم مهتمًا بالحصول على إشعار عندما تصل أسعار الأسهم إلى قيمة معينة (مشغل الحدث). يمكن للمستخدم أيضًا اختيار تلقي إشعار بمخزون معين كل ساعة (المشغل المجدول).

الآن بعد أن أصبحت الخطة جاهزة ، فلنضعها موضع التنفيذ!

إليك مستودع GitHub لهذا المشروع. إذا ضاعت في أي مكان من الكود أدناه ، فارجع إلى هذا المستودع وارجع إلى السرعة!

إحضار بيانات الأسهم باستخدام البرنامج النصي NodeJs

هذا ليس معقدًا كما يبدو! سيتعين علينا كتابة دالة تجلب البيانات باستخدام نقطة نهاية Alpha Vantage ويجب إطلاق استدعاء الجلب هذا في فترة 5 دقائق (لقد خمنت ذلك بشكل صحيح ، وعلينا وضع استدعاء الوظيفة هذا في setInterval ).

إذا كنت لا تزال تتساءل عن ماهية Alpha Vantage وتريد فقط إخراج هذا من رأسك قبل القفز إلى جزء الترميز ، فإليك ما يلي:

Alpha Vantage Inc. هي شركة رائدة في توفير واجهات برمجة التطبيقات المجانية للبيانات الواقعية والتاريخية عن الأسهم والفوركس (FX) والعملات الرقمية / العملات المشفرة.

سنستخدم نقطة النهاية هذه للحصول على المقاييس المطلوبة لسهم معين. تتوقع واجهة برمجة التطبيقات (API) هذه وجود مفتاح API كأحد المعلمات. يمكنك الحصول على مفتاح API المجاني من هنا. نحن الآن جيدون للوصول إلى الجزء المثير للاهتمام - فلنبدأ في كتابة بعض الأكواد!

تثبيت التبعيات

أنشئ دليل stocks-app وأنشئ دليل server بداخله. بدئه كمشروع عقدة باستخدام npm init ثم تثبيت التبعيات التالية:

 npm i isomorphic-fetch pg nodemon --save

هذه هي التبعيات الثلاث الوحيدة التي نحتاجها لكتابة هذا البرنامج النصي لجلب أسعار الأسهم وتخزينها في قاعدة بيانات Postgres.

فيما يلي شرح موجز لهذه التبعيات:

  • isomorphic-fetch
    يجعل من السهل استخدام fetch المتماثل (بنفس الشكل) على كل من العميل والخادم.
  • pg
    إنه عميل PostgreSQL غير محظور لـ NodeJs.
  • nodemon
    يقوم تلقائيًا بإعادة تشغيل الخادم عند تغيير أي ملف في الدليل.

إعداد التكوين

أضف ملف config.js على مستوى الجذر. أضف مقتطف الشفرة أدناه في هذا الملف في الوقت الحالي:

 const config = { user: '<DATABASE_USER>', password: '<DATABASE_PASSWORD>', host: '<DATABASE_HOST>', port: '<DATABASE_PORT>', database: '<DATABASE_NAME>', ssl: '<IS_SSL>', apiHost: 'https://www.alphavantage.co/', }; module.exports = config;

يرتبط user ، password ، host ، port ، database ، ssl بتكوين Postgres. سنعود لتعديل هذا بينما نقوم بإعداد جزء محرك Hasura!

تهيئة تجمع اتصال Postgres للاستعلام عن قاعدة البيانات

connection pool هو مصطلح شائع في علوم الكمبيوتر وغالبًا ما تسمع هذا المصطلح أثناء التعامل مع قواعد البيانات.

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

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

الآن بعد أن أصبح من الواضح ما هو تجمع الاتصال وما الذي يتم استخدامه من أجله ، فلنبدأ بإنشاء مثيل لتجمع اتصال pg لهذا التطبيق:

أضف ملف pool.js على مستوى الجذر وأنشئ مثيل تجمع على النحو التالي:

 const { Pool } = require('pg'); const config = require('./config'); const pool = new Pool({ user: config.user, password: config.password, host: config.host, port: config.port, database: config.database, ssl: config.ssl, }); module.exports = pool;

تُنشئ سطور التعليمات البرمجية أعلاه مثيلاً لـ Pool مع خيارات التكوين كما تم تعيينها في ملف التكوين. لم ننتهي بعد من ملف التكوين ولكن لن تكون هناك أية تغييرات تتعلق بخيارات التكوين.

لقد وضعنا الأرضية الآن ومستعدون لبدء إجراء بعض استدعاءات واجهة برمجة التطبيقات لنقطة نهاية Alpha Vantage.

دعنا ندخل في الشيء المثير للاهتمام!

إحضار بيانات الأسهم

في هذا القسم ، سنقوم بإحضار بيانات المخزون من نقطة نهاية Alpha Vantage. هذا ملف index.js :

 const fetch = require('isomorphic-fetch'); const getConfig = require('./config'); const { insertStocksData } = require('./queries'); const symbols = [ 'NFLX', 'MSFT', 'AMZN', 'W', 'FB' ]; (function getStocksData () { const apiConfig = getConfig('apiHostOptions'); const { host, timeSeriesFunction, interval, key } = apiConfig; symbols.forEach((symbol) => { fetch(`${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key}`) .then((res) => res.json()) .then((data) => { const timeSeries = data['Time Series (5min)']; Object.keys(timeSeries).map((key) => { const dataPoint = timeSeries[key]; const payload = [ symbol, dataPoint['2. high'], dataPoint['3. low'], dataPoint['1. open'], dataPoint['4. close'], dataPoint['5. volume'], key, ]; insertStocksData(payload); }); }); }) })()

لغرض هذا المشروع ، سنقوم بالاستعلام عن أسعار هذه الأسهم فقط - NFLX (Netflix) ، MSFT (Microsoft) ، AMZN (Amazon) ، W (Wayfair) ، FB (Facebook).

راجع هذا الملف للتعرف على خيارات التكوين. وظيفة getStocksData لا تفعل الكثير! وهي تدور عبر هذه الرموز وتستعلم عن نقطة نهاية Alpha Vantage ${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key} للحصول على مقاييس هذه الأسهم.

تضع وظيفة insertStocksData نقاط البيانات هذه في قاعدة بيانات Postgres. ها هي وظيفة insertStocksData :

 const insertStocksData = async (payload) => { const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)'; pool.query(query, payload, (err, result) => { console.log('result here', err); }); };

هذه هي! لقد جلبنا نقاط بيانات المخزون من واجهة برمجة تطبيقات Alpha Vantage وقمنا بكتابة وظيفة لوضعها في قاعدة بيانات Postgres في جدول stock_data . هناك قطعة واحدة مفقودة لجعل كل هذا يعمل! يجب علينا ملء القيم الصحيحة في ملف التكوين. سنحصل على هذه القيم بعد إعداد محرك Hasura. دعنا نصل إلى ذلك على الفور!

يرجى الرجوع إلى دليل server للحصول على الكود الكامل لجلب نقاط البيانات من نقطة نهاية Alpha Vantage وتعبئة ذلك في قاعدة بيانات Hasura Postgres.

إذا كانت طريقة إعداد الاتصالات وخيارات التكوين وإدخال البيانات باستخدام الاستعلام الأولي تبدو صعبة بعض الشيء ، فالرجاء عدم القلق بشأن ذلك! سنتعلم كيفية القيام بكل هذا بالطريقة السهلة باستخدام طفرة GraphQL بمجرد إعداد محرك Hasura!

إعداد محرك Hasura GraphQL

من السهل حقًا إعداد محرك Hasura والتشغيل مع مخططات GraphQL والاستعلامات والطفرات والاشتراكات ومحفزات الأحداث وغير ذلك الكثير!

انقر فوق Try Hasura وأدخل اسم المشروع:

إنشاء مشروع حصورة
إنشاء مشروع حصورة. (معاينة كبيرة)

أنا أستخدم قاعدة بيانات Postgres المستضافة على Heroku. قم بإنشاء قاعدة بيانات على Heroku واربطها بهذا المشروع. يجب أن تكون جاهزًا لتجربة قوة وحدة تحكم Hasura الغنية بالاستعلامات.

يرجى نسخ عنوان URL الخاص بـ Postgres DB الذي ستحصل عليه بعد إنشاء المشروع. سيتعين علينا وضع هذا في ملف التكوين.

انقر فوق Launch Console وستتم إعادة توجيهك إلى طريقة العرض هذه:

وحدة تحكم Hasura
وحدة تحكم Hasura. (معاينة كبيرة)

لنبدأ في بناء مخطط الجدول الذي سنحتاجه لهذا المشروع.

إنشاء مخطط جداول في قاعدة بيانات Postgres

الرجاء الانتقال إلى علامة التبويب "البيانات" والنقر فوق "إضافة جدول"! لنبدأ في إنشاء بعض الجداول:

جدول symbol

يستخدم هذا الجدول لتخزين معلومات الرموز. في الوقت الحالي ، احتفظت بحقلين هنا - id company . id الحقل هو مفتاح أساسي company من النوع varchar . دعنا نضيف بعض الرموز في هذا الجدول:

جدول الرموز
جدول symbol . (معاينة كبيرة)

جدول stock_data

يخزن جدول stock_data id symbol time والمقاييس مثل volume high low open close . سيتم استخدام البرنامج النصي NodeJs الذي كتبناه سابقًا في هذا القسم لملء هذا الجدول المحدد.

إليك كيف يبدو الجدول:

جدول stock_data
جدول stock_data . (معاينة كبيرة)

مرتب! دعنا ننتقل إلى الجدول الآخر في مخطط قاعدة البيانات!

جدول user_subscription

يخزن جدول user_subscription كائن الاشتراك مقابل معرف المستخدم. يتم استخدام كائن الاشتراك هذا لإرسال إشعارات الدفع عبر الويب إلى المستخدمين. سنتعلم لاحقًا في المقالة كيفية إنشاء كائن الاشتراك هذا.

يوجد حقلين في هذا الجدول - id هو المفتاح الأساسي لنوع uuid وحقل الاشتراك من النوع jsonb .

جدول events

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

  • id : هو مفتاح أساسي بخاصية الزيادة التلقائية.
  • symbol : حقل نصي.
  • user_id : من النوع uuid .
  • نوع trigger_type : يستخدم لتخزين نوع مشغل الحدث - time/event .
  • trigger_value : يستخدم لتخزين قيمة المشغل. على سبيل المثال ، إذا اختار المستخدم مشغل الحدث المستند إلى السعر - فهو يريد تحديثات إذا وصل سعر السهم إلى 1000 ، فإن قيمة trigger_value ستكون 1000 ويكون نوع trigger_type هو event .

هذه هي كل الجداول التي نحتاجها لهذا المشروع. يتعين علينا أيضًا إعداد العلاقات بين هذه الجداول للحصول على تدفق سلس للبيانات والاتصالات. لنفعل ذلك!

إقامة العلاقات بين الجداول

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

 events.user_id → user_subscription.id

يرتبط جدول stock_data بجدول الرموز على النحو التالي:

 stock_data.symbol → symbol.id

يتعين علينا أيضًا إنشاء بعض العلاقات على جدول symbol على النحو التالي:

 stock_data.symbol → symbol.id events.symbol → symbol.id

لقد أنشأنا الآن الجداول المطلوبة وأقمنا العلاقات فيما بينها! دعنا ننتقل إلى علامة التبويب GRAPHIQL في وحدة التحكم لرؤية السحر!

قام Hasura بالفعل بإعداد استعلامات GraphQL بناءً على هذه الجداول:

استعلامات / طفرات GraphQL على وحدة تحكم Hasura
استعلامات / طفرات GraphQL على وحدة تحكم Hasura. (معاينة كبيرة)

من السهل ببساطة الاستعلام عن هذه الجداول ويمكنك أيضًا تطبيق أي من هذه المرشحات / الخصائص ( distinct_on ، limit ، offset ، order_by ، where ) للحصول على البيانات المطلوبة.

يبدو كل هذا جيدًا ولكننا ما زلنا لم نقم بتوصيل رمز جانب الخادم الخاص بنا بوحدة التحكم Hasura. دعونا نكمل هذا الجزء!

ربط البرنامج النصي NodeJs بقاعدة بيانات Postgres

يرجى وضع الخيارات المطلوبة في ملف config.js في دليل server على النحو التالي:

 const config = { databaseOptions: { user: '<DATABASE_USER>', password: '<DATABASE_PASSWORD>', host: '<DATABASE_HOST>', port: '<DATABASE_PORT>', database: '<DATABASE_NAME>', ssl: true, }, apiHostOptions: { host: 'https://www.alphavantage.co/', key: '<API_KEY>', timeSeriesFunction: 'TIME_SERIES_INTRADAY', interval: '5min' }, graphqlURL: '<GRAPHQL_URL>' }; const getConfig = (key) => { return config[key]; }; module.exports = getConfig;

يرجى وضع هذه الخيارات من سلسلة قاعدة البيانات التي تم إنشاؤها عندما أنشأنا قاعدة بيانات Postgres على Heroku.

يتكون apiHostOptions من الخيارات ذات الصلة بواجهة برمجة التطبيقات مثل host key timeSeriesFunction interval .

ستحصل على حقل graphqlURL في علامة التبويب GRAPHIQL في وحدة التحكم Hasura.

تُستخدم الدالة getConfig لإرجاع القيمة المطلوبة من كائن التكوين. لقد استخدمنا هذا بالفعل في index.js في دليل server .

حان الوقت لتشغيل الخادم ونشر بعض البيانات في قاعدة البيانات. لقد أضفت برنامجًا نصيًا واحدًا في package.json على النحو التالي:

 "scripts": { "start": "nodemon index.js" }

قم بتشغيل npm start على المحطة الطرفية ويجب تعبئة نقاط البيانات الخاصة بمصفوفة الرموز في index.js في الجداول.

إعادة هيكلة الاستعلام الأولي في البرنامج النصي NodeJs إلى طفرة GraphQL

الآن بعد أن تم إعداد محرك Hasura ، دعنا نرى مدى سهولة استدعاء طفرة في جدول stock_data .

تستخدم الوظيفة insertStocksData في queries.js استعلامًا خامًا:

 const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';

دعنا نعيد التعامل مع هذا الاستعلام ونستخدم طفرة مدعومة بمحرك Hasura. ها هي queries.js تصنيعها في دليل الخادم:

 const { createApolloFetch } = require('apollo-fetch'); const getConfig = require('./config'); const GRAPHQL_URL = getConfig('graphqlURL'); const fetch = createApolloFetch({ uri: GRAPHQL_URL, }); const insertStocksData = async (payload) => { const insertStockMutation = await fetch({ query: `mutation insertStockData($objects: [stock_data_insert_input!]!) { insert_stock_data (objects: $objects) { returning { id } } }`, variables: { objects: payload, }, }); console.log('insertStockMutation', insertStockMutation); }; module.exports = { insertStocksData }

يرجى ملاحظة ما يلي: علينا إضافة graphqlURL في ملف config.js .

ترجع الوحدة النمطية apollo-fetch وظيفة الجلب التي يمكن استخدامها للاستعلام عن التاريخ / تغييره في نقطة نهاية GraphQL. سهل بما فيه الكفاية ، أليس كذلك؟

التغيير الوحيد الذي يتعين علينا القيام به في index.js هو إعادة كائن الأسهم بالتنسيق المطلوب بواسطة وظيفة insertStocksData . يرجى مراجعة index2.js و queries2.js للحصول على الكود الكامل باستخدام هذا الأسلوب.

الآن بعد أن أنجزنا جانب البيانات من المشروع ، دعنا ننتقل إلى بت الواجهة الأمامية ونبني بعض المكونات المثيرة للاهتمام!

ملاحظة : ليس علينا الاحتفاظ بخيارات تكوين قاعدة البيانات بهذا الأسلوب!

الواجهة الأمامية باستخدام عميل React و Apollo

مشروع الواجهة الأمامية موجود في نفس المستودع وتم إنشاؤه باستخدام حزمة create-react-app . يدعم عامل الخدمة الذي تم إنشاؤه باستخدام هذه الحزمة التخزين المؤقت للأصول ولكنه لا يسمح بإضافة المزيد من التخصيصات إلى ملف عامل الخدمة. توجد بالفعل بعض المشكلات المفتوحة لإضافة دعم لخيارات عامل الخدمة المخصص. هناك طرق للتخلص من هذه المشكلة وإضافة دعم لعامل خدمة مخصص.

لنبدأ بإلقاء نظرة على هيكل مشروع الواجهة الأمامية:

دليل المشروع
دليل المشروع. (معاينة كبيرة)

يرجى التحقق من دليل src ! لا تقلق بشأن الملفات المتعلقة بعامل الخدمة في الوقت الحالي. سنتعرف على المزيد حول هذه الملفات لاحقًا في هذا القسم. يبدو باقي هيكل المشروع بسيطًا. سيحتوي مجلد components على المكونات (Loader ، Chart) ؛ يحتوي مجلد services على بعض الوظائف / الخدمات المساعدة المستخدمة لتحويل الكائنات في الهيكل المطلوب ؛ styles كما يوحي الاسم تحتوي على ملفات sass المستخدمة لتصميم المشروع ؛ views هو الدليل الرئيسي ويحتوي على مكونات طبقة العرض.

سنحتاج فقط إلى عنصرين لعرض هذا المشروع - قائمة الرموز وسلسلة الرموز. سنقوم ببناء السلاسل الزمنية باستخدام مكون الرسم البياني من مكتبة Highcharts. لنبدأ في إضافة التعليمات البرمجية في هذه الملفات لبناء الأجزاء على الواجهة الأمامية!

تثبيت التبعيات

فيما يلي قائمة التبعيات التي سنحتاجها:

  • apollo-boost
    يعد Apollo Boost طريقة تكوين صفري لبدء استخدام عميل Apollo. يأتي مرفقًا مع خيارات التكوين الافتراضية.
  • reactstrap و bootstrap
    المكونات مبنية باستخدام هاتين الحزمتين.
  • graphql و graphql-type-json
    graphql هي تبعية مطلوبة لاستخدام apollo-boost و graphql-type-json تستخدم لدعم نوع json المستخدم في مخطط GraphQL.
  • highcharts و highcharts-react-official
    وسيتم استخدام هاتين الحزمتين لبناء المخطط:

  • node-sass
    يضاف هذا لدعم ملفات sass للتصميم.

  • uuid
    تستخدم هذه الحزمة لتوليد قيم عشوائية قوية.

كل هذه التبعيات ستكون منطقية بمجرد أن نبدأ في استخدامها في المشروع. دعنا ننتقل إلى الجزء التالي!

إعداد عميل Apollo

قم بإنشاء apolloClient.js داخل مجلد src على النحو التالي:

 import ApolloClient from 'apollo-boost'; const apolloClient = new ApolloClient({ uri: '<HASURA_CONSOLE_URL>' }); export default apolloClient;

يقوم الكود أعلاه بإنشاء مثيل ApolloClient ويستقبل uri في خيارات التكوين. عنوان uri هو عنوان URL لوحدة تحكم Hasura الخاصة بك. ستحصل على حقل uri هذا في علامة التبويب GRAPHIQL في قسم نقطة نهاية GraphQL .

يبدو الكود أعلاه بسيطًا ولكنه يعتني بالجزء الرئيسي من المشروع! يربط مخطط GraphQL المبني على Hasura بالمشروع الحالي.

يتعين علينا أيضًا تمرير كائن عميل Apollo هذا إلى ApolloProvider ولف مكون الجذر داخل ApolloProvider . سيؤدي هذا إلى تمكين جميع المكونات المتداخلة داخل المكون الرئيسي من استخدام استعلامات client وإطلاق الاستعلامات على كائن العميل هذا.

دعنا نعدل ملف index.js على النحو التالي:

 const Wrapper = () => { /* some service worker logic - ignore for now */ const [insertSubscription] = useMutation(subscriptionMutation); useEffect(() => { serviceWorker.register(insertSubscription); }, []) /* ignore the above snippet */ return <App />; } ReactDOM.render( <ApolloProvider client={apolloClient}> <Wrapper /> </ApolloProvider>, document.getElementById('root') );

الرجاء تجاهل إدراج رمز متعلق insertSubscription . سوف نفهم ذلك بالتفصيل لاحقًا. يجب أن تكون بقية التعليمات البرمجية سهلة الالتفاف. تأخذ وظيفة render مكون الجذر ومعرّف العنصر كمعلمات. يتم تمرير client الإشعار (مثيل ApolloClient) كخاصية إلى ApolloProvider . يمكنك التحقق من ملف index.js الكامل هنا.

إعداد عامل الخدمة المخصصة

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

يتعين علينا إرسال إشعارات الدفع عبر الويب لتحديثات أسعار الأسهم للمستخدمين المشتركين. دعونا نضع الأساس ونبني ملف عامل الخدمة هذا!

يقوم insertSubscription تم قصه في ملف index.js بعمل تسجيل عامل الخدمة ووضع كائن الاشتراك في قاعدة البيانات باستخدام ميزة subscriptionMutation .

يرجى الرجوع إلى queries.js لجميع الاستفسارات والطفرات المستخدمة في المشروع.

serviceWorker.register(insertSubscription); استدعاء وظيفة register المكتوبة في ملف serviceWorker.js . ها هو:

 export const register = (insertSubscription) => { if ('serviceWorker' in navigator) { const swUrl = `${process.env.PUBLIC_URL}/serviceWorker.js` navigator.serviceWorker.register(swUrl) .then(() => { console.log('Service Worker registered'); return navigator.serviceWorker.ready; }) .then((serviceWorkerRegistration) => { getSubscription(serviceWorkerRegistration, insertSubscription); Notification.requestPermission(); }) } }

تتحقق الوظيفة المذكورة أعلاه أولاً مما إذا كان serviceWorker مدعومًا من المستعرض ثم تسجل ملف عامل الخدمة المستضاف على عنوان URL swUrl . سنتحقق من هذا الملف بعد قليل!

تقوم وظيفة getSubscription بعمل الحصول على كائن الاشتراك باستخدام طريقة subscribe في كائن pushManager . ثم يتم تخزين كائن الاشتراك هذا في جدول user_subscription مقابل معرف مستخدم. يرجى ملاحظة أنه يتم إنشاء معرف المستخدم باستخدام وظيفة uuid . دعنا نتحقق من وظيفة getSubscription :

 const getSubscription = (serviceWorkerRegistration, insertSubscription) => { serviceWorkerRegistration.pushManager.getSubscription() .then ((subscription) => { const userId = uuidv4(); if (!subscription) { const applicationServerKey = urlB64ToUint8Array('<APPLICATION_SERVER_KEY>') serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }).then (subscription => { insertSubscription({ variables: { userId, subscription } }); localStorage.setItem('serviceWorkerRegistration', JSON.stringify({ userId, subscription })); }) } }) }

يمكنك التحقق من ملف serviceWorker.js للحصول على الكود الكامل!

نافذة إعلام منبثقة
نافذة إعلام منبثقة. (معاينة كبيرة)

قام Notification.requestPermission() باستدعاء هذه النافذة المنبثقة التي تطلب من المستخدم الإذن بإرسال الإخطارات. بمجرد أن ينقر المستخدم على السماح ، يتم إنشاء كائن اشتراك بواسطة خدمة الدفع. نقوم بتخزين هذا الكائن في LocalStorage على النحو التالي:

كائن اشتراكات Webpush
كائن اشتراكات Webpush. (معاينة كبيرة)

تُستخدم endpoint الحقل في الكائن أعلاه لتحديد الجهاز ويستخدم الخادم نقطة النهاية هذه لإرسال إشعارات الدفع عبر الويب إلى المستخدم.

لقد قمنا بأعمال تهيئة وتسجيل عامل الخدمة. لدينا أيضا موضوع الاشتراك للمستخدم! يعمل هذا بشكل جيد بسبب ملف serviceWorker.js الموجود في المجلد public . لنقم الآن بإعداد عامل الخدمة لتجهيز الأشياء!

هذا موضوع صعب بعض الشيء لكن دعونا نفهمه بشكل صحيح! كما ذكرنا سابقًا ، لا تدعم أداة create-react-app المساعدة التخصيصات افتراضيًا لعامل الخدمة. يمكننا تحقيق تنفيذ عامل خدمة العملاء باستخدام وحدة workbox-build .

يتعين علينا أيضًا التأكد من أن السلوك الافتراضي لملفات التخزين المؤقت المسبق سليم. سنقوم بتعديل الجزء حيث يتم بناء عامل الخدمة في المشروع. و workbox-build يساعد في تحقيق ذلك بالضبط! الأشياء الرائعة! دعنا نجعل الأمر بسيطًا ونقوم بإدراج كل ما يتعين علينا القيام به لجعل عامل الخدمة المخصصة يعمل:

  • تعامل مع التخزين المؤقت المسبق للأصول باستخدام workboxBuild .
  • قم بإنشاء قالب عامل خدمة لتخزين الأصول مؤقتًا.
  • قم بإنشاء ملف sw-precache-config.js لتوفير خيارات التكوين المخصصة.
  • أضف النص البرمجي لعامل خدمة البناء في خطوة الإنشاء في package.json .

لا تقلق إذا كان كل هذا يبدو محيرا! لا تركز المقالة على شرح الدلالات الكامنة وراء كل نقطة من هذه النقاط. علينا التركيز على جزء التنفيذ في الوقت الحالي! سأحاول تغطية السبب وراء القيام بكل العمل لتكوين عامل خدمة مخصص في مقال آخر.

لنقم بإنشاء ملفين sw-build.js و sw-custom.js -custom.js في دليل src . يرجى الرجوع إلى الروابط الخاصة بهذه الملفات وإضافة الرمز إلى مشروعك.

لنقم الآن بإنشاء ملف sw-precache-config.js على مستوى الجذر وإضافة الكود التالي في هذا الملف:

 module.exports = { staticFileGlobs: [ 'build/static/css/**.css', 'build/static/js/**.js', 'build/index.html' ], swFilePath: './build/serviceWorker.js', stripPrefix: 'build/', handleFetch: false, runtimeCaching: [{ urlPattern: /this\\.is\\.a\\.regex/, handler: 'networkFirst' }] }

لنعدّل أيضًا ملف package.json لإفساح المجال لبناء ملف عامل الخدمة المخصص:

أضف هذه العبارات في قسم scripts :

 "build-sw": "node ./src/sw-build.js", "clean-cra-sw": "rm -f build/precache-manifest.*.js && rm -f build/service-worker.js",

وتعديل سكربت build على النحو التالي:

 "build": "react-scripts build && npm run build-sw && npm run clean-cra-sw",

تم الإعداد أخيرًا! علينا الآن إضافة ملف عامل خدمة مخصص داخل المجلد public :

 function showNotification (event) { const eventData = event.data.json(); const { title, body } = eventData self.registration.showNotification(title, { body }); } self.addEventListener('push', (event) => { event.waitUntil(showNotification(event)); })

لقد أضفنا للتو مستمعًا واحدًا للاستماع إلى push الدفع التي يرسلها الخادم. تُستخدم وظيفة showNotification لعرض إشعارات الدفع على الويب للمستخدم.

هذه هي! لقد انتهينا من كل العمل الشاق المتمثل في إعداد عامل خدمة مخصص للتعامل مع إشعارات الدفع عبر الويب. سنرى هذه الإخطارات قيد التنفيذ بمجرد أن نبني واجهات المستخدم!

نحن نقترب من بناء أجزاء الكود الرئيسية. لنبدأ الآن بالمنظر الأول!

عرض قائمة الرموز

يبدو مكون App المستخدم في القسم السابق كما يلي:

 import React from 'react'; import SymbolList from './views/symbolList'; const App = () => { return <SymbolList />; }; export default App;

إنه مكون بسيط يقوم بإرجاع طريقة عرض SymbolList وتقوم SymbolList بكل الأعمال الشاقة لعرض الرموز في واجهة مستخدم مرتبطة بدقة.

لنلقِ نظرة على symbolList.js داخل مجلد views العرض:

يرجى الرجوع إلى الملف هنا!

يُرجع المكون نتائج دالة renderSymbols . ويتم جلب هذه البيانات من قاعدة البيانات باستخدام الخطاف useQuery على النحو التالي:

 const { loading, error, data } = useQuery(symbolsQuery, {variables: { userId }});

يتم تعريف symbolsQuery على النحو التالي:

 export const symbolsQuery = gql` query getSymbols($userId: uuid) { symbol { id company symbol_events(where: {user_id: {_eq: $userId}}) { id symbol trigger_type trigger_value user_id } stock_symbol_aggregate { aggregate { max { high volume } min { low volume } } } } } `;

يأخذ userId ويجلب الأحداث التي تم الاشتراك بها لهذا المستخدم المحدد لعرض الحالة الصحيحة لأيقونة الإشعار (رمز الجرس الذي يتم عرضه مع العنوان). يقوم الاستعلام أيضًا بجلب القيم القصوى والدقيقة للمخزون. لاحظ استخدام aggregate في الاستعلام أعلاه. تقوم استعلامات تجميع avg بالعمل خلف الكواليس لجلب القيم sum مثل count ، والجمع ، والمتوسط ​​، max ، min ، وما إلى ذلك.

بناءً على الاستجابة من استدعاء GraphQL أعلاه ، إليك قائمة البطاقات المعروضة على الواجهة الأمامية:

بطاقات المخزون
بطاقات المخزون. (معاينة كبيرة)

تبدو بنية HTML للبطاقة كما يلي:

 <div key={id}> <div className="card-container"> <Card> <CardBody> <CardTitle className="card-title"> <span className="company-name">{company} </span> <Badge color="dark" pill>{id}</Badge> <div className={classNames({'bell': true, 'disabled': isSubscribed})} id={`subscribePopover-${id}`}> <FontAwesomeIcon icon={faBell} title="Subscribe" /> </div> </CardTitle> <div className="metrics"> <div className="metrics-row"> <span className="metrics-row--label">High:</span> <span className="metrics-row--value">{max.high}</span> <span className="metrics-row--label">{' '}(Volume: </span> <span className="metrics-row--value">{max.volume}</span>) </div> <div className="metrics-row"> <span className="metrics-row--label">Low: </span> <span className="metrics-row--value">{min.low}</span> <span className="metrics-row--label">{' '}(Volume: </span> <span className="metrics-row--value">{min.volume}</span>) </div> </div> <Button className="timeseries-btn" outline onClick={() => toggleTimeseries(id)}>Timeseries</Button>{' '} </CardBody> </Card> <Popover className="popover-custom" placement="bottom" target={`subscribePopover-${id}`} isOpen={isSubscribePopoverOpen === id} toggle={() => setSubscribeValues(id, symbolTriggerData)} > <PopoverHeader> Notification Options <span className="popover-close"> <FontAwesomeIcon icon={faTimes} onClick={() => handlePopoverToggle(null)} /> </span> </PopoverHeader> {renderSubscribeOptions(id, isSubscribed, symbolTriggerData)} </Popover> </div> <Collapse isOpen={expandedStockId === id}> { isOpen(id) ? <StockTimeseries symbol={id}/> : null } </Collapse> </div>

نحن نستخدم مكون Card في ReactStrap لعرض هذه البطاقات. يتم استخدام مكون Popover لعرض الخيارات القائمة على الاشتراك:

خيارات الإعلام
خيارات الإعلام. (معاينة كبيرة)

عندما ينقر المستخدم على أيقونة bell لسهم معين ، يمكنه الاشتراك للحصول على إشعار كل ساعة أو عندما يصل سعر السهم إلى القيمة التي تم إدخالها. سنرى هذا في العمل في قسم الأحداث / مشغلات الوقت.

ملاحظة : سنصل إلى مكون StockTimeseries في القسم التالي!

يرجى الرجوع إلى symbolList.js للحصول على الكود الكامل المتعلق بمكون قائمة الأسهم.

عرض الجداول الزمنية للمخزون

يستخدم مكوِّن StockTimeseries الاستعلام stocksDataQuery :

 export const stocksDataQuery = gql` query getStocksData($symbol: String) { stock_data(order_by: {time: desc}, where: {symbol: {_eq: $symbol}}, limit: 25) { high low open close volume time } } `;

يقوم الاستعلام أعلاه بجلب آخر 25 نقطة بيانات للسهم المحدد. على سبيل المثال ، يوجد هنا الرسم البياني لمقياس فتح سهم Facebook:

الجدول الزمني لأسعار الأسهم
الجدول الزمني لأسعار الأسهم. (معاينة كبيرة)

هذا مكون مباشر حيث نمرر في بعض خيارات الرسم البياني إلى مكون [ HighchartsReact ]. فيما يلي خيارات الرسم البياني:

 const chartOptions = { title: { text: `${symbol} Timeseries` }, subtitle: { text: 'Intraday (5min) open, high, low, close prices & volume' }, yAxis: { title: { text: '#' } }, xAxis: { title: { text: 'Time' }, categories: getDataPoints('time') }, legend: { layout: 'vertical', align: 'right', verticalAlign: 'middle' }, series: [ { name: 'high', data: getDataPoints('high') }, { name: 'low', data: getDataPoints('low') }, { name: 'open', data: getDataPoints('open') }, { name: 'close', data: getDataPoints('close') }, { name: 'volume', data: getDataPoints('volume') } ] }

يعرض المحور السيني الوقت ويعرض المحور الصادي قيمة المقياس في ذلك الوقت. تُستخدم وظيفة getDataPoints لتوليد سلسلة من النقاط لكل سلسلة.

 const getDataPoints = (type) => { const values = []; data.stock_data.map((dataPoint) => { let value = dataPoint[type]; if (type === 'time') { value = new Date(dataPoint['time']).toLocaleString('en-US'); } values.push(value); }); return values; }

بسيط! هذه هي الطريقة التي يتم بها إنشاء مكون المخطط! يرجى الرجوع إلى ملفات stockTimeseries.js للحصول على الكود الكامل في السلاسل الزمنية للمخزون.

يجب أن تكون الآن جاهزًا بالبيانات وواجهات المستخدم كجزء من المشروع. دعنا ننتقل الآن إلى الجزء المثير للاهتمام - إعداد مشغلات الحدث / الوقت بناءً على مدخلات المستخدم.

إعداد الحدث / المشغلات المجدولة

في هذا القسم ، سنتعلم كيفية إعداد المشغلات على وحدة تحكم Hasura وكيفية إرسال إشعارات الويب إلى المستخدمين المحددين. هيا بنا نبدأ!

مشغلات الأحداث على وحدة التحكم Hasura

لنقم بإنشاء حدث مشغل stock_value في الجدول stock_data insert تشغيل. سيتم تشغيل الويب هوك في كل مرة يوجد فيها إدراج في جدول stock_data .

إعداد مشغلات الحدث
إعداد مشغلات الحدث. (معاينة كبيرة)

سنقوم بإنشاء مشروع خلل لعنوان URL الخاص بخطاف الويب. اسمحوا لي أن أتحدث قليلاً عن webhooks لتسهيل فهمها:

يتم استخدام Webhooks لإرسال البيانات من تطبيق إلى آخر عند حدوث حدث معين. عندما يتم تشغيل حدث ما ، يتم إجراء استدعاء HTTP POST إلى عنوان URL الخاص بـ webhook مع بيانات الحدث كحمولة.

In this case, when there is an insert operation on the stock_data table, an HTTP post call will be made to the configured webhook URL (post call in the glitch project).

Glitch Project For Sending Web-push Notifications

We've to get the webhook URL to put in the above event trigger interface. Go to glitch.com and create a new project. In this project, we'll set up an express listener and there will be an HTTP post listener. The HTTP POST payload will have all the details of the stock datapoint including open , close , high , low , volume , time . We'll have to fetch the list of users subscribed to this stock with the value equal to the close metric.

These users will then be notified of the stock price via web-push notifications.

That's all we've to do to achieve the desired target of notifying users when the stock price reaches the expected value!

Let's break this down into smaller steps and implement them!

Installing Dependencies

We would need the following dependencies:

  • express : is used for creating an express server.
  • apollo-fetch : is used for creating a fetch function for getting data from the GraphQL endpoint.
  • web-push : is used for sending web push notifications.

Please write this script in package.json to run index.js on npm start command:

 "scripts": { "start": "node index.js" }

Setting Up Express Server

Let's create an index.js file as:

 const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json()); const handleStockValueTrigger = (eventData, res) => { /* Code for handling this trigger */ } app.post('/', (req, res) => { const { body } = req const eventType = body.trigger.name const eventData = body.event switch (eventType) { case 'stock-value-trigger': return handleStockValueTrigger(eventData, res); } }); app.get('/', function (req, res) { res.send('Hello World - For Event Triggers, try a POST request?'); }); var server = app.listen(process.env.PORT, function () { console.log(`server listening on port ${process.env.PORT}`); });

In the above code, we've created post and get listeners on the route / . get is simple to get around! We're mainly interested in the post call. If the eventType is stock-value-trigger , we'll have to handle this trigger by notifying the subscribed users. Let's add that bit and complete this function!

جلب المستخدمين المشتركين

 const fetch = createApolloFetch({ uri: process.env.GRAPHQL_URL }); const getSubscribedUsers = (symbol, triggerValue) => { return fetch({ query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }`, variables: { symbol, triggerValue } }).then(response => response.data.events) } const handleStockValueTrigger = async (eventData, res) => { const symbol = eventData.data.new.symbol; const triggerValue = eventData.data.new.close; const subscribedUsers = await getSubscribedUsers(symbol, triggerValue); const webpushPayload = { title: `${symbol} - Stock Update`, body: `The price of this stock is ${triggerValue}` } subscribedUsers.map((data) => { sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload)); }) res.json(eventData.toString()); }

في الدالة handleStockValueTrigger أعلاه ، نقوم أولاً بإحضار المستخدمين المشتركين باستخدام وظيفة getSubscribedUsers . نقوم بعد ذلك بإرسال إشعارات الدفع عبر الويب إلى كل من هؤلاء المستخدمين. تُستخدم الوظيفة sendWebpush لإرسال الإشعارات. سننظر في تطبيق Web-push في غضون لحظة.

تستخدم الدالة getSubscribedUsers الاستعلام:

 query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }

يأخذ هذا الاستعلام رمز المخزون والقيمة ويجلب تفاصيل المستخدم بما user-id user_subscription الذي يطابق الشروط التالية:

  • يساوي symbol الذي يتم تمريره في الحمولة.
  • trigger_type يساوي event .
  • trigger_value أكبر من أو تساوي الوظيفة التي يتم تمريرها إلى هذه الوظيفة ( close في هذه الحالة).

بمجرد أن نحصل على قائمة المستخدمين ، فإن الشيء الوحيد المتبقي هو إرسال إشعارات عبر الويب إليهم! دعونا نفعل ذلك على الفور!

إرسال إخطارات Web-Push إلى المستخدمين المشتركين

يتعين علينا أولاً الحصول على مفاتيح VAPID العامة والخاصة لإرسال إشعارات الدفع عبر الويب. يرجى تخزين هذه المفاتيح في ملف .env وتعيين هذه التفاصيل في index.js على النحو التالي:

 webPush.setVapidDetails( 'mailto:<YOUR_MAIL_ID>', process.env.PUBLIC_VAPID_KEY, process.env.PRIVATE_VAPID_KEY ); const sendWebpush = (subscription, webpushPayload) => { webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err)) }

يتم استخدام وظيفة sendNotification لإرسال دفعة الويب على نقطة نهاية الاشتراك المقدمة كمعامل أول.

هذا كل ما هو مطلوب لإرسال إشعارات الدفع عبر الويب بنجاح إلى المستخدمين المشتركين. إليك الكود الكامل المحدد في index.js :

 const express = require('express'); const bodyParser = require('body-parser'); const { createApolloFetch } = require('apollo-fetch'); const webPush = require('web-push'); webPush.setVapidDetails( 'mailto:<YOUR_MAIL_ID>', process.env.PUBLIC_VAPID_KEY, process.env.PRIVATE_VAPID_KEY ); const app = express(); app.use(bodyParser.json()); const fetch = createApolloFetch({ uri: process.env.GRAPHQL_URL }); const getSubscribedUsers = (symbol, triggerValue) => { return fetch({ query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }`, variables: { symbol, triggerValue } }).then(response => response.data.events) } const sendWebpush = (subscription, webpushPayload) => { webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err)) } const handleStockValueTrigger = async (eventData, res) => { const symbol = eventData.data.new.symbol; const triggerValue = eventData.data.new.close; const subscribedUsers = await getSubscribedUsers(symbol, triggerValue); const webpushPayload = { title: `${symbol} - Stock Update`, body: `The price of this stock is ${triggerValue}` } subscribedUsers.map((data) => { sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload)); }) res.json(eventData.toString()); } app.post('/', (req, res) => { const { body } = req const eventType = body.trigger.name const eventData = body.event switch (eventType) { case 'stock-value-trigger': return handleStockValueTrigger(eventData, res); } }); app.get('/', function (req, res) { res.send('Hello World - For Event Triggers, try a POST request?'); }); var server = app.listen(process.env.PORT, function () { console.log("server listening"); });

دعنا نختبر هذا التدفق من خلال الاشتراك في مخزون مع بعض القيمة وإدخال هذه القيمة يدويًا في الجدول (للاختبار)!

اشتركت في AMZN بقيمة 2000 ثم أدخلت نقطة بيانات في الجدول بهذه القيمة. إليك الطريقة التي أعلمني بها تطبيق إشعار الأسهم بعد الإدراج مباشرةً:

إدراج صف في جدول stock_data للاختبار
إدراج صف في جدول stock_data للاختبار. (معاينة كبيرة)

مرتب! يمكنك أيضًا التحقق من سجل استدعاء الحدث هنا:

سجل الأحداث
سجل الأحداث. (معاينة كبيرة)

الويب هوك يقوم بالعمل كما هو متوقع! نحن جميعًا على استعداد لبدء تشغيل الحدث الآن!

مشغلات مجدولة / كرون

يمكننا تحقيق مشغل يستند إلى الوقت لإخطار المستخدمين المشتركين كل ساعة باستخدام مشغل حدث Cron على النحو التالي:

إعداد الكرون / الزناد المجدول
إعداد الكرون / الزناد المجدول. (معاينة كبيرة)

يمكننا استخدام نفس عنوان URL الخاص بخطاف الويب والتعامل مع المستخدمين المشتركين بناءً على نوع حدث المشغل مثل stock_price_time_based_trigger . التنفيذ مشابه للمشغل المستند إلى الحدث.

خاتمة

في هذه المقالة ، قمنا ببناء تطبيق إشعار أسعار الأسهم. تعلمنا كيفية جلب الأسعار باستخدام واجهات برمجة تطبيقات Alpha Vantage وتخزين نقاط البيانات في قاعدة بيانات Postgres المدعومة من Hasura. تعلمنا أيضًا كيفية إعداد محرك Hasura GraphQL وإنشاء مشغلات قائمة على الأحداث ومجدولة. قمنا ببناء مشروع خلل لإرسال إشعارات الويب إلى المستخدمين المشتركين.