اذهب إلى المحتوى

كل الأنشطة

تحدث تلقائيًا

  1. الساعة الماضية
  2. لا نجيب هنا في الأكاديمية على أسئلة الإختبارات ولكن يمكننى توضيح فكرة الحل لكي . يمكنكي إستخدام حلقة التكرار while لتحقيق ذلك. أولا يجب سؤال المستخدم عن عدد العناصر إذا أردتي أن يقوم هو بتحديد عدد العناصر أو يمكنك أنتي تحديد عدد العناصر فى المصفوفة وبعد ذلك وضع هذه القيمة في متغير يشير إلى حجم المصفوفة. ثم بعد ذلك نقوم بإنشاء متغير يحوى قيمة صفر , ثم بعد ذلك ننشأ مصفوفة فارغة . ثم نقوم بعمل while loop وجعل الشرط هو التاكد من أن المتغير الذى قمنا بوضع قيمة إبتدائية له ليس اكبر من المتغير size الذى قام المستخدم بإدخاله وفي كل مرة داخل ال loop نقوم بسؤال المستخدم عن الرقم الجديد الذى يريد إضافته للمصفوفة وبعد ذلك نقم بوضعه داخل المصفوفة وبعد ذلك لا ننسى أن نقوم بزيادة قيمة المتغير ب 1 حتى لا يتم تنفيذ ال loop دائما . وهكذا قد تم إنشاء القائمة لدينا . يمكنك كتابة الكود بنفسك وإذا واجهتك أى مشكلة به فقط أخبرينى
  3. هذا الخطأ في بايثون ينتج عندما تحاولين تحويل سلسلة نصية تحتوى على أرقام عشرية وليست رقم صحيح . مثل هذا الكود . print(int('1.5')) الكود السابق سيظهر نفس الخطأ الذى ظهر لديكي . ولحل تلك المشكلة يمكنك إستخدام split ولكن الأفضل هو تحويل الرقم إلى float ثم بعد ذلك int هكذا . print(int(float('1.5'))) وهكذا فإن الكود السابق سيقوم بطباعة رقم 1 ويمكنك إستخدامه وتمرير القيمة التى أدخلها المستخدم لأنه إذا قام بإدخال رقم عشرى و لم يتم التعامل معه سيظل يظهر الخطأ السابق
  4. كيف ممكن اسمح للمستخدم ادخال اكثر من قيمة باستخدام int(input("")) لعمل مصفوفة من قبل المستخدم ؟؟
  5. invalid literal for int() with base 10: ما المقصود بهذا الخطأ ؟؟؟ وكيف ممكن احل او اتعامل معه ؟ ما هو نوع هذا الخطأ؟ وهل ممكن استخدم .split() مع (int(input
  6. تلك أمور بنكية، لذا أنت بحاجة إلى إختيار بنك به شروط تناسبك في بلدك، ثم التوجه لأقرب فرع منه إليك، ثم التحدث إلى خدمة العملاء في الفرع وسيتم فتح حساب وإنشاء بطاقة إئتمانية إن كانت الشروط متوافقة معك. وتستطيع التحدث إلى خدمة العملاء عبر الهاتف لمعرفة الشروط المطلوبة أو تصفح موقع البنك عبر الإنترنت.
  7. اليوم
  8. وعليكم السلام اهم شيء قبل جميع النصائح هو الاستمرارية والفضول حول تعلم كل ما هو جديد والان سأعطيك بعض الخطوات : حاول حل تحديات يومياً أو بانتظام لتحسين مهاراتك تدريجيا بدأ بحل التحديات السهلة وتدرج في الصعوبة بمرور الوقت. حل الكثير من التحديات السهلة سيساعدك على بناء أساس قوي. عد حل أي تحدٍ، انظر إلى الحلول المقدمة من الآخرين. هذا سيساعدك على معرفة طرق مختلفة لحل المشكلة ويمكن أن يقدم لك أفكار جديدة. شارك في المنتديات والأسئلة والنقاشات على Codewars. للاستفادة من نصائح من المبرمجين الأكثر خبرة. حاول حل تحديات في مواضيع مختلفة (مثل الخوارزميات، هياكل البيانات، الرياضيات، إلخ) لتوسيع معرفتك ومهاراتك. إذا واجهت صعوبة في حل تحدٍ معين، راجع المواد التعليمية ذات الصلة وحاول مرة أخرى. التكرار هو مفتاح التعلم. وبالتوفيق إن شاء الله ...
  9. يمكنك الانتقال إلى المستوى المتقدم أي (Kyu 1 - Kyu 2) عند تحقيقك لمستوى معين من المهارات والخبرة، في العادة تتبع هذه العملية بناء على التقدم التدريجي من المستويات السهلة إلى المستويات الأكثر صعوبة، حيث يجب عليك كسب نقاط كافية من خلال حل التحديات، و يتم تصنيف التحديات بناء على مستوى صعوبتها، والمستويات الأسهل تمنحك نقاطا أقل بينما تمنحك المستويات الأصعب نقاطا أكثر، فكلما حليت عددا أكبر من التحديات وخصوصا التحديات المتوسطة والصعبة ، زادت فرصتك أكثر للانتقال إلى المستويات المتقدمة.
  10. السلام عليكم امتي اتنقل لمستوي المتقدم في موقع codewars ؟ ملحوظ ان بتدا من اسهل حاجه
  11. نكمل في هذا المقال تطوير تطبيق الواجهة البرمجية REST الذي بدأناه في مقال سابق، حيث نضيف للتطبيق المميزات التالية: Mongoose التي تسمح لنا بالعمل مع قاعدة البيانات MongoDB بدلًا من كائن DAO المقيم في الذاكرة. استيثاق Authentication ﻹضافة إمكانيات منح اﻷذونات كي يتمكن مستثمرو الواجهة البرمجية من استخدام مفتاح تشفير ويب JSON Web Token للوصول بأمان إلى نقاط وصول الواجهة البرمجية. اختبارات مؤتمتة: باستخدام إطار عمل الاختبارات Mocha، والمكتبة Chai والوحدة البرمجية SuperTest، للمساعدة في التحقق من المحدودية عند زيادة حجم الشيفرة وتغيّرها. كما سنضيف تباعًا مكتبات للتحقق من صحة المدخلات ومكتبات خاصة بآمان التطبيق، ,وسنكتسب بعض الخبرة في التعامل مع مدير الحاويات Docker. تثبيت MongoDB على هيئة حاوية لنبدأ باستبدال قاعدة البيانات المقيمة في الذاكرة التي استخدمناها مسبقا بقاعدة بيانات حقيقة هي MongoDB. باﻹمكان طبعًا تثبيت القاعدة محليًا على جهازك، لكن ونظرًا للاختلافات بين بيئات التشغيل (إصدارات مختلفة لنظام التشغيل مثلًا) والتي قد تسبب بعض المشكلات، سنستخدم مدير الحاويات Docker وهو أداة معيارية حاليًا في صناعة البرمجيات. وكل ما عليك فعله هو تثبيت Docker ثم Docker Compose، وتأكد بعدها من التثبيت بتنفيذ اﻷمر docker -v على الطرفية كي ترى نسخة Docker Composer التي ثُبتت على جهازك. لتشغيل MongoDBضمن جذر المشروع، سننشئ أولًا ملف YAML يُدعى docker.compose.yml يضم الشيفرة التالية: version: '3' services: mongo: image: mongo volumes: - ./data:/data/db ports: - "27017:27017" يتيح Docker Composer تشغيل عدة حاويات في نفس الوقت باستخدام ملف تهيئة واحد. لهذا سنستخدمه اﻵن لتشغيل MongoDB دون أن نثبتها على الجهاز: sudo docker-compose up -d يشغّل اﻷمر up الحاوية المحددة، مصغيًا إلى منفذ MongoDB27017، بينما تفصل d- اﻷمر عن الطرفية ليعمل التطبيق مستقلًا، وستكون النتيجة مشابهة لما يلي إذا جرى كل شيء دون مشاكل: Creating network "toptal-rest-series_default" with the default driver Creating toptal-rest-series_mongo_1 ... done تنشئ التعليمة السابقة أيضًا الملجد في جذر المشروع، لهذا عليك إضافة سطر بالمجلد data في الملف الذي ينتهي باللاحقة gitignore. وعندما نريد إيقاف الحاوية التي تحتوي MongoDB، علينا فقط تنفيذ الأمر sudo docker -compose down: Stopping toptal-rest-series_mongo_1 ... done Removing toptal-rest-series_mongo_1 ... done Removing network toptal-rest-series_default هذا كل ما تحتاج معرفته لتشغيل MongoDB مع الواجهة البرمجية REST. تأكد اﻵن من تنفيذ اﻷمر sudo docker-compose up -d حتى تكون MongoDB جاهزة للاستخدام مع التطبيق. استخدام Mongoose للوصول إلى MongoDB نستخدم مكتبة نمذجة البيانات Mongoose في الاتصال مع قاعدة البيانات MongoDB. وعلى الرغم من سهول استخدام Mongoose، سيفيدك الاطلاع على توثيقها لتعلّم كل اﻹمكانيات التي تقدمها للتطبيقات الفعلية. استخدم سطر اﻷوامر التالي لتثبيت Mongoose: npm i mongoose لنبدأ بإعداد خدمة Mongoose ﻹدارة الاتصال مع قاعدة البيانات MongoDB، وطالما أن عدة موارد تتشارك هذه الخدمة، سنضيفها إلى الملجد common مباشرة. نستخدم أيضًا الكائن mongooseOptions لتخصيص خيارات Mongoose التالية (على الرغم من أن استخدامه ليس إلزاميًا): useNeewUrlPrser: إن لم يُضبط هذا الخيار على القيمة true ستعرض Mongoose تحذير بأن هذه الخاصية عرضة للإهمال في النسخ المستقبلية depreciation warning. useUnified Topology: يوصي توثيق Mongoose بضبط قيمة هذا الخيار على true، وذلك لاستخدام محرك إدارة اتصال أحدث. serverSelectionTimeoutMS: لغايات تصميمية تتعلق بتطبيقنا، وفي حال نسيت تشغيل MongoDB قبل Node.js، سيكون اختيارك مدة أقل من المدة الافتراضية 30 ثانية سبيلًا لعرض معلومات عن MongoDB مباشرةً بانتظار استجابة الواجهة الخلفية. useFindAndModify: تلغي القيمة false لهذا الخيار عرض التحذير deprecation warning، وقد أشار توثيق Mongoose إلى التحذير الناتج عن هذا الخيار من بين جميع الخيارات اﻷخرى. إذ يدفع هذا الخيار Mongoose لاستخدام ميزات أصلية أحدث لقاعدة البيانات MongoDB بدلًا من كائنات Mongoose أقدم. إليك الشيفرة النهائية للملف common/services/mongoose.service.ts بعد جمع الخيارات السابقة مع بعض اﻹعدادات المتعلقة بإعادة محاولة الاتصال: import mongoose from 'mongoose'; import debug from 'debug'; const log: debug.IDebugger = debug('app:mongoose-service'); class MongooseService { private count = 0; private mongooseOptions = { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 5000, useFindAndModify: false, }; constructor() { this.connectWithRetry(); } getMongoose() { return mongoose; } connectWithRetry = () => { log('Attempting MongoDB connection (will retry if needed)'); mongoose .connect('mongodb://localhost:27017/api-db', this.mongooseOptions) .then(() => { log('MongoDB is connected'); }) .catch((err) => { const retrySeconds = 5; log( `MongoDB connection unsuccessful (will retry #${++this .count} after ${retrySeconds} seconds):`, err ); setTimeout(this.connectWithRetry, retrySeconds * 1000); }); }; } export default new MongooseService(); تأكد أنك تميّز بين الدالة ()connect التي توفرها مكتبة Mongoose وبين دالة الخدمة الخاصة بنا connectWithRetry: تحاول mongoose.connect الاتصال بخدمة MongoDB الخاصة بنا (التي نشغلها باستخدام docker-compose) والذي ينتهي بعد زمن يُحدده الخيار serverSelectionTimeoutMS بالميلي ثانية. تعيد الدالة connectWithRetry محاولة الاتصال السابقة في حال بدأ التطبيق العمل قبل عمل خدمة MongoDB. وطالما أنها دالة بانية لصنف متفرّد singleton، وبالتالي ستعمل هذه الدالة مرة واحدة، لكنها ستحاول استدعاء ()connect باستمرار يتخلل ذلك فترة توقف مدتها retrySeconds ثانية عند ينتهي الوقت المخصص لمحاولة الاتصال. ستكون الخطوة التالية استبدال البيانات المقيمة في الذاكرة بقاعدة البيانات MongoDB! إزالة قاعدة البيانات المقيمة في الذاكرة وإضافة MongoDB استخدمنا سابقًا قاعدة البيانات المقيمة في الذاكرة كي نركّز في عملنا على بقية الوحدات البرمجية التي نبنيها. ولكي نستخدم MongoDB اﻵن، علينا أن نعيد بناء الملف users.dao.ts بالكامل، ويتطلب اﻷمر بداية إدراج اعتمادية أخرى: import mongooseService from '../../common/services/mongoose.service'; نزيل اﻵن كل ما يتعلق بتعريف الصنف UserDao ما عدا الدالة البانية، ثم نعيد بناءه بإنشاء تخطيط Schema لبيانات المستخدم يعمل مع Mongoose قبل الدالة البانية: Schema = mongooseService.getMongoose().Schema; userSchema = new this.Schema({ _id: String, email: String, password: { type: String, select: false }, firstName: String, lastName: String, permissionFlags: Number, }, { id: false }); User = mongooseService.getMongoose().model('Users', this.userSchema); تعرّف الشيفرة السابقة مجموعة Mongoose الخاصة بنا، وتضيف ميزة خاصة لا تمتلكها قاعدة البيانات المقيمة في الذاكرة وهي الخيار select:false للحقل password لإخفاء هذا الحقل كلما طلبنا قائمة بأسماء المستخدمين. قد يبدو تخطيط المستخدم مألوفًا فهو يشبه تخطيط كائن نقل البيانات DTO الذي تعاملنا معه، لكن الفرق الرئيسي هو أننا نعرّف الحقول التي يجب أن تتواجد ضمن مجموعة MongoDB التي تُدعى Users، بينما يُعرّف الكائن DTO الحقول المقبولة في طلبات HTTP. مع ذلك لن يتغير هذا الجزء المتعلق بكائن DTO، إذ سندرج كائنات DTO الثلاث التي عرّفناها سابقًا في أعلى الملف users.dao.ts. لكن وقبل كتابة شيفرة العمليات اﻷساسية CRUD على قاعدة البيانات، سنجري تغييرين على كائنات DTO. التغير اﻷول على كائن DTO: الحقل id_ مقابل الحقل id نزيل الحقل id من كائنات DT لأن Mongoose تقدم الحقل id_ تلقائيًا، إذ يأتي ضمن معاملات طلب الوجهة (المسار route). وانتبه إلى أن نماذج Mongoose تزوّدنا بمستخلص افتراضي للمعرّف id، لهذا عطلّنا هذا الخيار كالتالي:{ id: false } لتفادي أي التباس. ولأن هذا اﻷمر يعطّل مرجعنا إلى user.id ضمن الدالة الوسيطة ()validateSameEmailBelongToSameUser، سنتحتاج إلى user._id بدلًا عنه. تستخدم بعض قواعد البيانات id وتستخدم أخرى id_ للإشارة إلى حقل المعرّف. لكن في تطبيقنا الذي يستخدم Mongoose، انتبهنا ببساطة إلى أي ترميز نستخدمه في كل مرحلة من مراحل كتابة الشيفرة، لكن مع ذلك سيظهر الاختلاف لمستثمري الواجهة البرمجية كما يوضح الشكل التالي: (تظهر الصورة أعلاه استخدام وظهور معرفات المستخدم في مشروع الواجهة البرمجية REST بصورته النهائية. ولاحظ كيف يختلف ترميز حقل المعرّف باختلاف مصدر المعرّف سواء كان معامل لطلب مباشر، أو بيانات مشفرة بطريقة JWT أو سجل قاعدة بيانات مُحضر حديثًا). التغيير الثاني: إعداد السماحيات المبنية على تفعيل رايات محددة سنغيّر أيضًا الراية permissionLevel إلى permissionFlags في كائنات DTO لنعكس عملنا على منظومة سماحيات أكثر تعقيدًا، إضافة إلى تعريف مخطط Mongoose السابق userSchema. كائنات DTO ومبدأ عدم التكرار DRY تذكّر أن ما يضمه كائن DTO هي فقط الحقول التي نريد تمريرها بين الواجهة البرمجية وقاعدة البيانات، وقد يبدو اﻷمر بأنه تداخل أو تكرار في البيانات الموجودة في نموذج التعامل مع البيانات وتلك الموجودة في كائنات DTO وانتهاكًا لمبدأ "عدم التكرار"، لكن لا تبالغ في تطبيق هذا المبدأ على حساب اﻷمان الذي يكون في أفضل حالاته مع اﻹعدادات الافتراضية. فعندما يتطلب اﻷمر مثلًا إضافة حقل في مكان واحد، من المحتمل عندها أن يعرضه المطوّرون في الواجهة البرمجية دون قصد وغير منتبهين إلى أن تخزين البيانات ونقلها سياقان منفصلان وقد يتطلب كلًا منهما مجموعات مختلفة من المتطلبات. يمكننا بعد الانتهاء من التغييرات على كائنات DTO تنفيذ توابع العمليات اﻷساسية CRUD (بعد الدالة البانية UsersDao)، وسنبدأ بعملية إنشاء مستخدم جديد: async addUser(userFields: CreateUserDto) { const userId = shortid.generate(); const user = new this.User({ _id: userId, ...userFields, permissionFlags: 1, }); await user.save(); return userId; } لاحظ أننا نلغي قيمة permissionFlags التي قد يرسلها مستثمر الواجهة البرمجية عبر userFields, ونستبدلها بالقيمة 1. ننتقل تاليًا إلى عملية القراءة التي لها وظائف الحصول على مستخدم من خلال معرّفه أو من خلال البريد اﻹلكتروني أو الحصول على قائمة المستخدمين ضمن صفحات: async getUserByEmail(email: string) { return this.User.findOne({ email: email }).exec(); } async getUserById(userId: string) { return this.User.findOne({ _id: userId }).populate('User').exec(); } async getUsers(limit = 25, page = 0) { return this.User.find() .limit(limit) .skip(limit * page) .exec(); } ستكفي دالة DAO واحدة لتحديث مستخدم لأن دالة Mongoose في الخلفية findOneAndUpdate قادرة على تحديث المستند بأكمله أو تحديث جزء منه. وانتبه إلى أن دالتنا الخاصة تأخذ أحد القيمتين PatchUserDto أو PutUserDto للمعامل userFields من خلال استخدام نوع الاجتماع (|) في TypeScript: async updateUserById( userId: string, userFields: PatchUserDto | PutUserDto ) { const existingUser = await this.User.findOneAndUpdate( { _id: userId }, { $set: userFields }, { new: true } ).exec(); return existingUser; } يبلغ الخيار new: true مكتبة Mongoose بأن تعيد الكائن كما هو بعد التحديث بدلًا عما كانه أصلًا. أما عملية الحذف فهي موجزة في Mongoose: async removeUserById(userId: string) { return this.User.deleteOne({ _id: userId }).exec(); } قد يلاحظ القارئ أن كل استدعاء للدوال اﻷعضاء للصنف User مقترن باستدعاء للدالة ()exec، وهذا اﻷمر اختياري لكنه مفضّل بين مطوري Mongoose لأنه يقدم طريقة أفضل في تتبع حالة المكدس عند التنقيح. علينا اﻻن بعد الانتهاء من كتابة شيفرة كائن DAO تحديث الملف users.service.ts قليلًا حتى يتماشى مع الدوال الجديدة. ولا حاجة هنا ﻹعادة بناء الملف، بل فقط ثلاث لمسات بسيطة: @@ -16,3 +16,3 @@ class UsersService implements CRUD { async list(limit: number, page: number) { - return UsersDao.getUsers(); + return UsersDao.getUsers(limit, page); } @@ -20,3 +20,3 @@ class UsersService implements CRUD { async patchById(id: string, resource: PatchUserDto): Promise<any> { - return UsersDao.patchUserById(id, resource); + return UsersDao.updateUserById(id, resource); } @@ -24,3 +24,3 @@ class UsersService implements CRUD { async putById(id: string, resource: PutUserDto): Promise<any> { - return UsersDao.putUserById(id, resource); + return UsersDao.updateUserById(id, resource); } يبقى استدعاء بقية الدوال كما هو تمامًا، لأننا أبقينا الهيكلية التي أنشأناها سابقًا على الرغم من إعادة بناء الصنف UsersDao مع بعض الاستثناءات: نستخدم اﻵن الدالة ()updateUserById لتنفيذ العمليتين PUT و PATCH (والسبب كما ذكرنا سابقًا أننا ندعم النهج النمطي لتنفيذ واجهات برمجية REST، بعيدًا عن بعض التوصيات التي لا تدعم استخدام PUT في إنشاء موارد جديدة غير موجودة أصلًا كي لا تسمح الواجهة الخلفية لمستثمريها التحكم بتولد المعرّفات ID). طالما أن كائن DAO الجديد يستخدم المعاملين limit و page، سنمررهما إلى الدالة ()getUsers. تقدّم الهيكلية السابقة نموذجًا متماسكًا، إذ يرفض أية محاولة مثلًا لاستبدال MongoDB Mongoose بمكتبات أخرى مثل TypefORM و PostgreSL. ولتنفيذ مثل هذه التغييرات لا بد من إعادة بناء كل دالة من دوال DAO مع الحفاظ على بصمة هذه الدوال signature لتتفق مع بقية الشيفرة. اختبار الواجهة البرمجية REST المبنية على Mongoose لنشغّل الواجهة البرمجية باستخدام سطر اﻷوامر npm start، ونحاول إنشاء مستخدم: curl --request POST 'localhost:3000/users' \ --header 'Content-Type: application/json' \ --data-raw '{ "password":"secr3tPass!23", "email":"marcos.henrique@toptal.com" }' يتضمن كائن الاستجابة معرّف المستخدم الجديد: { "id": "7WYQoVZ3E" } وسيكون إجراء بقية الاختبارات يديويًا أسهل باستخدام متغيرات البيئة: REST_API_EXAMPLE_ID="put_your_id_here" يبدو تحديث مستخدم كالتالي: curl --include --request PATCH "localhost:3000/users/$REST_API_EXAMPLE_ID" \ --header 'Content-Type: application/json' \ --data-raw '{ "firstName": "Marcos", "lastName": "Silva" }' من المفترض ان تبدأ الاستجابة بالترويسة HTTP/1.1 204 No Content. (لاحظ أنه بدون include-- لن تُطبع أية استجابة). ولكي تتحقق من نجاح الطلبين السابقين نفّذ ما يلي: curl --request GET "localhost:3000/users/$REST_API_EXAMPLE_ID" \ --header 'Content-Type: application/json' \ --data-raw '{ "firstName": "Marcos", "lastName": "Silva" }' تعرض الاستجابة الحقول المتوقعة ومن بينها id_ الذي ناقشناه سابقًا: { "_id": "7WYQoVZ3E", "email": "marcos.henrique@toptal.com", "permissionFlags": 1, "__v": 0, "firstName": "Marcos", "lastName": "Silva" } لاحظ وجود حقل خاص هو v__ يُستخدم من قبل Mongoose لتحديد الإصدار وسيتغير في كل مرة يُحدَّث فيها نفس السجّل. دعونا اﻵن نطلب قائمة بأسماء المستخدمين: curl --request GET 'localhost:3000/users' \ --header 'Content-Type: application/json' وستكون الاستجابة نفسها، لكنها مغلفة بالقوسين []. بعد أن تأكدنا أن كلمة المرور قد حُفظت بأمان، سنتحقق من إمكانية حذف المستخدم: curl --include --request DELETE "localhost:3000/users/$REST_API_EXAMPLE_ID" \ --header 'Content-Type: application/json' نتوقع أن نحصل هنا على الاستجابة 204 مجددًا. قد تتسائل إن كان حقل كلمة المرور يعمل كما نتوقع، طالما أننا حددنا الخيار select: false في تعريف تخطيط Mongoose وبالتالي لن يظهر الحقل عند الاستجابة للطلب GET. لهذا سنكرر إنشاء مستخدم جديد ونتحرى اﻷمر (لا تنسى تخزين المعرّف الجديد لاستخدامات لاحقة). كلمات المرور المخفية والتنقيح المباشر للبيانات في حاوية MongoDB للتحقق من التخزين اﻵمن لكلمات المرور (مشفرّة بدلًا من أن تظهر كما هي)، يمكن للمطورين تفحّص MongoDB مباشرة. وأحد الطرق الممكنة استخدام واجهة سطر اﻷوامر المعيارية mongo من داخل حاوية Docker قيد التشغيل: sudo docker exec -it toptal-rest-series_mongo_1 mongo من هناك نفّذ اﻷمر use api-db يتبعه ()db.users.find().pretty لتحصل على بيانات جميع المستخدمين بما فيها كلمات المرور. أما لمن يفضل العمل على واجهات رسومية، بإمكانهم تثبيت عميل MongoDB مثل Robo 3T: تُعد البادئة $...argon2 جزءًا من التنسيق النصي PHC وقد خّزنت دون تعديل عن قصد. فإظهار عبارة Argon2 ومعاملاتها العامة لن يساعد المخترقين على تحديد كلمة النص السرياﻷصلية إن استطاعوا سرقة قاعدة البيانات. ويمكن تعزيز كلمة المرور أكثر باستخدام التغفيل salting، وهي تقنية سنستخدمها لاحقًا مع مفتاح JWT. تأكدنا اﻵن أن Mongoose ترسل البيانات بنجاح إلى MongoDB، لكن كيف نتأكد أن مستثمري الواجهة البرمجية سيرسلون بيانات مناسبة ضمن طلباتهم إلى الوجهات الخاصة المستخدم؟ إضافة المكتبة express-validator هنالك طرق عدة للتحقق من الطلبات، سنستخدم منها في تطبيقنا المكتبة express-validator وهي مكتبة مستقرة وسهلة الاستخدام وتتمتع بتوثيق جيد. وعلى الرغم من إمكانية استخدام وظيفة التحقق المضمنة مع Mongoose، لكن الميزات التي Express.js يقدمها express-validator أكثر. إذ تقدم لك المكتبة متحققًا جاهزًا من صحة البريد اﻹلكتروني، بينما عليك كتابة شيفرته بنفسك في Mongoose. سنثبت اﻵن المكتبة express-validator: npm i express-validator ولكي نحدد الحقول التي نتحقق من صحتها، نستخدم التابع ()body الذي سندرجه في الملف users.routes.config.ts. يتحقق بعدها التابع ()bodyمن الحقول ويولّد قائمة باﻷخطاء مخزّنة في الكائن express.Request في حال أخفق الطلب، ونحتاج عندها إلى أداة وسيطة كي تستفيد من تلك القائمة وتتحقق من اﻷخطاء. وطالما أننا نستخدم نفس المنطق على بقية المسارات، سننشئ الملف common/middleware/body.validation.middleware.ts الذي يضم الشيفرة التالية: import express from 'express'; import { validationResult } from 'express-validator'; class BodyValidationMiddleware { verifyBodyFieldsErrors( req: express.Request, res: express.Response, next: express.NextFunction ) { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).send({ errors: errors.array() }); } next(); } } export default new BodyValidationMiddleware(); وهكذا سنكون قادرين على التعامل مع أية أخطاء تولّدها الدالة ()body. لنضف اﻵن ما يلي إلى الملف users.routes.config.ts: import BodyValidationMiddleware from '../common/middleware/body.validation.middleware'; import { body } from 'express-validator'; نستطيع اﻵن تحديث وجهاتنا كالتالي: @@ -15,3 +17,6 @@ export class UsersRoutes extends CommonRoutesConfig { .post( - UsersMiddleware.validateRequiredUserBodyFields, + body('email').isEmail(), + body('password') + .isLength({ min: 5 }) + .withMessage('Must include password (5+ characters)'), + BodyValidationMiddleware.verifyBodyFieldsErrors, UsersMiddleware.validateSameEmailDoesntExist, @@ -28,3 +33,10 @@ export class UsersRoutes extends CommonRoutesConfig { this.app.put(`/users/:userId`, [ - UsersMiddleware.validateRequiredUserBodyFields, + body('email').isEmail(), + body('password') + .isLength({ min: 5 }) + .withMessage('Must include password (5+ characters)'), + body('firstName').isString(), + body('lastName').isString(), + body('permissionFlags').isInt(), + BodyValidationMiddleware.verifyBodyFieldsErrors, UsersMiddleware.validateSameEmailBelongToSameUser, @@ -34,2 +46,11 @@ export class UsersRoutes extends CommonRoutesConfig { this.app.patch(`/users/:userId`, [ + body('email').isEmail().optional(), + body('password') + .isLength({ min: 5 }) + .withMessage('Password must be 5+ characters') + .optional(), + body('firstName').isString().optional(), + body('lastName').isString().optional(), + body('permissionFlags').isInt().optional(), + BodyValidationMiddleware.verifyBodyFieldsErrors, UsersMiddleware.validatePatchEmail, لا تنس إضافة BodyValidationMiddleware.verifyBodyFieldsErrors في كل وجهة بعد أي سطر يضم الدالة ()body وإلا لن يكون لها فائدة. لاحظ أيضًا كيف حدّثنا الوجهتين PUT و POST لاستخدام express-validater بدلًا من الدالة validateRequiredUserBodyFields التي بنيناها بأنفسنا. لأنهما الوجهتان الوحيدتان اللتان تستخدمان تلك الدالة. ويمكن حذف شيفرتها من الملف users.middleware.ts. هذا كل ما في اﻷمر، شغّل Node.js وجرّب باستخدام أي عميل REST تفضلّه كيف تتعامل الواجهة البرمجية مع المدخلات المختلفة. ولا تنس اﻹطلاع على توثيق express-validator لترى اﻹمكانيات اﻷخرى التي تقدّمها. فما فعلنا هو مجرد نقطة انطلاق. الاستيثاق والسماحيات (منح التصريحات) تتيح واجهتنا البرمجية مجموعة من نقاط الوصول endpoints لمستثمري الواجهة كي ينشئوا ويعدّلوا المستخدمين ويطلبو قوائم بهم، وتمنح كل نقطة منها وصولًا عامًا غير محدود. وتمنع أنماط الاستخدام أي مستخدم من تغيير بيانات آخر، كما تمنع أي دخلاء من الوصول إلى أية نقطة لا نريد وصولًا عامًا إليها. تتضمن القيود السابقة ناحيتين أساسيتين نختصرهما بالعبارة "auth" وتعني الإذن: اﻷولى هي الاستيثاق authentication وتعني معرفة صاحب الطلب، والثانية هي الترخيص أو التصريح authorization أي السماح لصاحب الطلب بتنفيذ طلبه أو لا. لهذا انتبه جيدًا إلى الناحية التي تُناقش، وخاصة في طلبات HTTP المعيارية، فالحالة 401 Unauthorized تتعلق بالاستيثاق، أما 403 Forbidden فتتعلق بالتصريح ولهذا السبب سنستخدم الاصطلاح "auth" ليعني الاستيثاق، ونستخدم المصطلح سماحيات permissions لمواضيع التصاريح. هنالك العديد من نُهج الاستيثاق التي يمكنك الاطلاع عليها، بما في ذلك الخدمات التي يقدمها طرف ثالث مثل Ath0، لكننا سنستخدم في تطبيقنا نهجًا أبسط مبني على مفاتيح JWT لكنه قابل للتوسع. يتكون مفتاح JWT من نص JSON مشفّر مع بعض البيانات الوصفية التي لا تتعلق بالاستيثاق، وتضم في حالتنا البريد اﻹلكتروني للمستخدم ورايات السماحيات permissions flags. كما يضم نص JSON نصًا سرًيا Secret للتحقق من سلامة البيانات الوصفية. إن الفكرة التي نتبعها هي الطلب من العميل إرسال JWT ضمن كل طلب غير عام، مما يسمح لنا بالتحقق من امتلاك العميل الثبوتيات الصحيحة لاستخدام نقطة الوصول المطلوبة دون الحاجة إلى إرسال الثبوتيات بأنفسهم عبر قناة الاتصال عند كل طلب. لكن في أي مكان سنكتب الشيفرة المناسبة؟ يمكن من خلال الدوال الوسيطة استخدامها عندما تكون ضمن إعدادات الوجهات (المسارات). إضافة وحدة الاستيثاق لنحضّر أولًا ما يضمه مفتاح JWT، إذ سنبدأ استعمال الحقل permissionFlags العائد إلى مورد المستخدم لأنه فقط يقدم بيانات وصفية ملائمة للتشفير ضمن مفاتيح JWT، وليس لوجود أي علاقة بين مفاتيح JWT ومنطق تحديد السماحيات. سنضيف قبل إنشاء أداة وسيطة middleware لتوليد مفاتيح JWT دالة خاصة إلى الملف users.dao.ts تستخلص حقل كلمة المرور، لأننا أعددنا Mongoose لتمنع عرضه: async getUserByEmailWithPassword(email: string) { return this.User.findOne({ email: email }) .select('_id email permissionFlags +password') .exec(); } أضف ما يلي في الملف users.dao.ts: async getUserByEmailWithPassword(email: string) { return UsersDao.getUserByEmailWithPassword(email); } ننشئ اﻵن المجلد auth في جذر المشروع، ونضيف نقطة وصول لنسمح لمستثمري الواجهة البرمجية بتوليد مفاتيح JWT. لهذا ننشئ أولًا الوحدة الوسيطة auth/middleware/auth.middleware.ts على شكل صنف متفرّد singleton يُدعى AuthMiddleware، كما سنحتاج إلى استيراد بعض المكتبات: import express from 'express'; import usersService from '../../users/services/users.service'; import * as argon2 from 'argon2'; ننشئ ضمن الصنف AuthMiddleware دالة تتحقق من وجود ثبوتيات صالحة ضمن طلبات مستخدمي الواجهة البرمجية: async verifyUserPassword( req: express.Request, res: express.Response, next: express.NextFunction ) { const user: any = await usersService.getUserByEmailWithPassword( req.body.email ); if (user) { const passwordHash = user.password; if (await argon2.verify(passwordHash, req.body.password)) { req.body = { userId: user._id, email: user.email, permissionFlags: user.permissionFlags, }; return next(); } } // إعطاء نفس الرسالة في كلتا الحالتين // يساعد في الحماية من محاولات الاختراق res.status(400).send({ errors: ['Invalid email and/or password'] }); } ولكي تتأكد الدالة الوسيطة من وجود الحقلين email و password في جسم الطلب req.body، نستخدم express-validator، عندما نهيئ لاحقًا المسار لاستخدام الدالة ()verifyUserPassword. تخزين النص السري ضمن مفتاح JWT نحتاج إلى سر كي نوّلد مفتاح JWT، الذي نستخدمه في تعليم المفتاح وللتحقق من صحة المفاتيح القادمة مع طلبات العملاء. سنخزّن هذا النص السريضمن ملف متغيّر بيئة env. منفصل بدلًا من كتابته ضمن ملف TypeScript، ولن يُدفع هذا الملف إلى مستودع الشيفرة. وكما جرت العادة، أضفنا الملف env.example. إلى المستودع لمساعدة المطورين على فهم أي المتغيرات مطلوبة عند إنشاء الملف env. الحقيقي. ونحتاج في حالتنا إلى متغير يُدعى JWT_SECRET يُخزّن سر مفتاح JWT على هيئة نص. وتذكّر أن تغيّر هذا النص السريفي نسختك إن أردت العمل على التطبيق انطلاقًا من المستودع الذي يضم المشروع المكتمل في نهاية هذه السلسلة من المقالات. كما تجدر اﻹشارة هنا إلى ضرورة اتباع الممارسات اﻷفضل عند استخدام مفاتيح JWT بالتمييز بين المفاتيح وفق البيئة (تطوير، إنتاج،…). ينبغي على الملف env. الموجود في جذر المشروع اتباع التنسيق التالي، لكن ليس بالضرورة استخدام نفس السر: JWT_SECRET=My!@!Se3cr8tH4sh3 ومن الطرق السهلة لتحميل هذه المتغيرات ضمن التطبيق استخدام مكتبة تُدعى dotenv: npm i dotenv وكل ما تحتاجه لتهيئتها هو استدعاء الدالة ()dotenv.config حالما تشغّل التطبيق. لهذا أضف الشيفرة التالية أعلى الملف app.ts: import dotenv from 'dotenv'; const dotenvResult = dotenv.config(); if (dotenvResult.error) { throw dotenvResult.error; } متحكم الاستيثاق آخر المتطلبات التي تلزمنا قبل توليد مفتاح JWT هو تثبيت المكتبة jsonwebtoken وأنواع TypeScript الخاصة بها: npm i jsonwebtoken npm i --save-dev @types/jsonwebtoken لننشئ اﻵن المتحكم auth/controllers/auth.controller.ts، ولا حاجة ﻹدراج المكتبة dotenv لأن إدراجها في ملف التطبيق app.ts جعل محتوى ملف متغيرات البيئة متاحًا ضمن كامل التطبيق من خلال كائن Node.js العام process: import express from 'express'; import debug from 'debug'; import jwt from 'jsonwebtoken'; import crypto from 'crypto'; const log: debug.IDebugger = debug('app:auth-controller'); /** * تُنشر هذه القيمة تلقائيًا من ملف متغيرات البيئة الذي عليك إنشاءه بنفسك في جذر المشروع *في المستودع لمعرفة التنسيق المطلوب .env.example اطلع على الملف */ // @ts-expect-error const jwtSecret: string = process.env.JWT_SECRET; const tokenExpirationInSeconds = 36000; class AuthController { async createJWT(req: express.Request, res: express.Response) { try { const refreshId = req.body.userId + jwtSecret; const salt = crypto.createSecretKey(crypto.randomBytes(16)); const hash = crypto .createHmac('sha512', salt) .update(refreshId) .digest('base64'); req.body.refreshKey = salt.export(); const token = jwt.sign(req.body, jwtSecret, { expiresIn: tokenExpirationInSeconds, }); return res .status(201) .send({ accessToken: token, refreshToken: hash }); } catch (err) { log('createJWT error: %O', err); return res.status(500).send(); } } } export default new AuthController(); تربط المكتبة المفتاح الجديد بالنص السري الذي حددناه jwtSecret. كما سنوّلد مُوهِم (أو مُغفِّل salt) ومعمّي hash باستخدام الوحدة البرمجية اﻷصلية crypto في Node.js، وبعدها ننشئ المفتاح الذي يتمكن مستثمرو الواجهة من تحديث مفتاح JWT الحالي، وهذا اﻷمر مفيد بشكل خاص عند توسيع التطبيق. لكن ما الفرق بين refreshKey و refreshToken و accessToken؟ تُرسل هذه المفاتيح إلى مستثمر الواجهة البرمجية والغاية من ذلك هو: استخدام accessToken للطلبات التي تقع خارج إطار الوصول العام، و refreshToken لطلب استبدال مفتاح accessToken منتهي الصلاحية. ويستخدم المفتاح refreshKey لتمرير المتغير salt مشفّرًا ضمن المفتاح refreshToken إلى دالة التحديث الوسيطة التي سنشرحها لاحقًا. تجدر الملاحظة إلى أننا اعتمدنا على المكتبة jsonwebtoken في تحديد مدة صلاحية المفتاح، فإن انتهت صلاحية مفتاح لا بد من إعادة الاستيثاق من جديد: مسار التحقق اﻷساسية في الواجهة البرمجية REST المبنية على Node.js لنهيئ نقطة الوصول ضمن الملف auth/auth.routes.config.ts: import { CommonRoutesConfig } from '../common/common.routes.config'; import authController from './controllers/auth.controller'; import authMiddleware from './middleware/auth.middleware'; import express from 'express'; import BodyValidationMiddleware from '../common/middleware/body.validation.middleware'; import { body } from 'express-validator'; export class AuthRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'AuthRoutes'); } configureRoutes(): express.Application { this.app.post(`/auth`, [ body('email').isEmail(), body('password').isString(), BodyValidationMiddleware.verifyBodyFieldsErrors, authMiddleware.verifyUserPassword, authController.createJWT, ]); return this.app; } } لا تنس إضافة التالي إلى الملف app.ts: // ... import { AuthRoutes } from './auth/auth.routes.config'; // ... routes.push(new AuthRoutes(app)); //Userroutes قد يكون قبل أو بعد // ... أصبحنا اﻵن مستعدين ﻹعادة تشغيل Node.js واختبار الشيفرة، ولا بد من استخدام نفس الثبوتيات التي استخدمناها عند إنشاء المستخدم سابقًا: curl --request POST 'localhost:3000/auth' \ --header 'Content-Type: application/json' \ --data-raw '{ "password":"secr3tPass!23", "email":"marcos.henrique@toptal.com" }' ستكون الاستجابة شبيهة بالتالي: { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJVZGdzUTBYMXciLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicHJvdmlkZXIiOiJlbWFpbCIsInBlcm1pc3Npb25MZXZlbCI6MSwicmVmcmVzaEtleSI6ImtDN3JFdDFHUmNsWTVXM0N4dE9nSFE9PSIsImlhdCI6MTYxMTM0NTYzNiwiZXhwIjoxNjExMzgxNjM2fQ.cfI_Ey4RHKbOKFdVGsowePZlMeX3fku6WHFu0EMjFP8", "refreshToken": "cXBHZ2tJdUhucERaTVpMWVNTckhNenQwcy9Bd0VIQ2RXRnA4bVBJbTBuQVorcS9Qb2xOUDVFS2xEM1RyNm1vTGdoWWJqb2xtQ0NHcXhlWERUcG81d0E9PQ==" } وكما فعلنا سابقًا، سنستخدم متغيرات البيئة لاستخدام القيم السابقة بشكل ملائم: REST_API_EXAMPLE_ACCESS="put_your_access_token_here" REST_API_EXAMPLE_REFRESH="put_your_refresh_token_here" وهكذا نكون قد حصلنا على مفتاح الوصول ومفتاح التحديث لكننا نحتاج إلى أداة وسيطة للاستفادة منهما. وحدة وسيطة للتعامل مع JWT نحتاج بداية إلى نوع TypeScript جديد للتعامل مع بنية المفتاح JWT بشكلها غير المرمّز، لهذا أنشئ الملف الذي يضم الشيفرة التالية common/types/jwt.ts: export type Jwt = { refreshKey: string; userId: string; permissionFlags: string; }; لنكتب اﻵن شيفرة الدوال الوسيطة التي تتحقق من وجود مفتاح تحديث، والتحقق منه، والتحقق من مفتاح JWT. وسنضع الدوال الثلاث في الملف الجديد common/types/jwt.ts: import express from 'express'; import jwt from 'jsonwebtoken'; import crypto from 'crypto'; import { Jwt } from '../../common/types/jwt'; import usersService from '../../users/services/users.service'; // @ts-expect-error const jwtSecret: string = process.env.JWT_SECRET; class JwtMiddleware { verifyRefreshBodyField( req: express.Request, res: express.Response, next: express.NextFunction ) { if (req.body && req.body.refreshToken) { return next(); } else { return res .status(400) .send({ errors: ['Missing required field: refreshToken'] }); } } async validRefreshNeeded( req: express.Request, res: express.Response, next: express.NextFunction ) { const user: any = await usersService.getUserByEmailWithPassword( res.locals.jwt.email ); const salt = crypto.createSecretKey( Buffer.from(res.locals.jwt.refreshKey.data) ); const hash = crypto .createHmac('sha512', salt) .update(res.locals.jwt.userId + jwtSecret) .digest('base64'); if (hash === req.body.refreshToken) { req.body = { userId: user._id, email: user.email, permissionFlags: user.permissionFlags, }; return next(); } else { return res.status(400).send({ errors: ['Invalid refresh token'] }); } } validJWTNeeded( req: express.Request, res: express.Response, next: express.NextFunction ) { if (req.headers['authorization']) { try { const authorization = req.headers['authorization'].split(' '); if (authorization[0] !== 'Bearer') { return res.status(401).send(); } else { res.locals.jwt = jwt.verify( authorization[1], jwtSecret ) as Jwt; next(); } } catch (err) { return res.status(403).send(); } } else { return res.status(401).send(); } } } export default new JwtMiddleware(); تتحقق الدالة ()validRefreshNeeded أيضًا إذا ما كان مفتاح التحديث صحيحًا من أجل معرّف مستخدم محدد. فإن كان كذلك، سنستخدم الدالة authController.createJWT لتوليد مفتاح JWT جديد للمستخدم. وتتحقق الدالة ()validJWTNeeded, إن أرسل مستثمر الواجهة البرمجية مفتاح JWT صالح ضمن ترويسة طلب HTTP وفق الصيغة Authorization: Bearer <token> (وهذا أيضًا للأسف تضارب آخر بين الاستيثاق والتصريح). علينا اﻵن تهيئة مسار جديد لتحديث المفتاح، وقد شُفِّرت ضمنه رايات السماحية. مسار لتحديث مفاتيح JWT سندرج اﻷداة الوسطية الجديدة ضمن الملف auth.routes.config.ts: import jwtMiddleware from './middleware/jwt.middleware'; ثم سنضيف المسارات التالية: this.app.post(`/auth/refresh-token`, [ jwtMiddleware.validJWTNeeded, jwtMiddleware.verifyRefreshBodyField, jwtMiddleware.validRefreshNeeded, authController.createJWT, ]); سنختبر الآن إن كانت تعمل كما هو مطلوب باستخدام المفتاحين accessToken و refreshToken اللذان حصلنا عليهما سابقًا: curl --request POST 'localhost:3000/auth/refresh-token' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $REST_API_EXAMPLE_ACCESS" \ --data-raw "{ \"refreshToken\": \"$REST_API_EXAMPLE_REFRESH\" }" علينا أن نتوقع الحصول على مفتاحين جديدين accessToken و refreshToken كي نستخدمهما لاحقًا. بإمكانك اﻵن تجريب كيف تتحقق الواجهة الخلفية من المفتاحين السابقين، وكيف تحدد عدد المرات التي يمكنك فيها الحصول على مفاتيح جديدة. واﻵن سيتمكن مستثمرو الواجهة الخلفية من إنشاء مفاتيح JWT والتحقق منها وتحديثها. الخلاصة أكملنا في هذا المقال العمل على تطبيقنا الذي يُنشئ واجهة برمجة REST باستخدام TypeScript و Express.js في بيئة Node.js وقد انتقلنا في هذا القسم من قاعدة بيانات مؤقتة مقيمة في الذاكرة إلى استخدام قاعدة بيانات MongoDB بالاستعانة بالمكتبة Mongoose كما عملنا على تنفيذ آلية للتحقق من صحة المدخلات باستخدام مفاتيح ويب JWT والمكتبة jsonwebtoken. أما عن مفاهيم السماحيات وكيفية تنفيذها لتتكامل مع مفاتيح JWT فهذا ما سنراه في المقال التالي. ترجمة -وبتصرف- للقسم اﻷول من المقال Building a Node.js TypeScript REST API, Part 3 MongoDB, Authentication, and Automated Tests اقرأ أيضًا المقال السابق: بناء واجهة برمجية متوافقة مع REST باستخدام Express.js القسم الثاني: نماذج البيانات والبرمجيات الوسيطة والخدمات دمج قاعدة البيانات MongoDB في تطبيقك Node تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية مقارنة بين MySQL و MongoDB
  12. شكراا لحضرتك جداا يعني انا لما ارفع كود لنموذج ذكاء اصطناعي سواء git اوkaggle يفضل ان يكون من النوع ده ipynb صح كده ؟ سواء علي git او kaggle
  13. .py هي ملفات بايثون تحتوي على أكواد بايثون فقط، بينما .ipynb هي ملفات Jupyter Notebook وبداخلها ستجد أنه يوجد نتائج التنفيذ للكود والرسومات البيانية وإعدادات الملف.
  14. وعليكم السلام ورحمة الله وبركاته . يبدوا أنك تقصد jupyter notebook . إن هناك فرق كبير بين jupyter notebook و vscode . حيث أن vs code هو IDE متكامل تقوم فيه بكتابة وتحرير الأكواد حيث يساعدك فى سرعة الكتابة والتطوير وهو يحتوى على العديد من الإضافات التى تساعدك فى جميع المجالات وليس مجال تعلم الآلة فقط . أما jupyter notebook فهو أداة قوية ومفيدة في تعلم البرمجة وعلوم البيانات وتطوير النماذج الذكاء الاصطناعي حيث يمكنك من تجربة الكود ورؤية النتائج على الفور داخل البيئة نفسها. ويساعدك في عرض ورسم البيانات. ولذلك فإن الأفضل في مجال تعلم الاله هو jupyter notebook لما يوفره من بيئه كامله وأدوات تفاعليه قويه
  15. تمام بس هنا علي colab الملفات امتدادها ipynb فا برد الفرق بينها وبين امتداد py
  16. عليك استخدام Jupyter Notebook، فبينما توفر أدوات مثل Visual Studio Code (VSCode) و Visual Studio بيئة لكتابة وتشغيل أكواد بايثون، يبرز Jupyter Notebook كأداة لا غنى عنها في مجال تعلم الآلة، وذلك لكونه يوفر طريقة فريدة لعرض الرسوم البيانية ونماذج التعلم الآلي، مما يُتيح لك فهمًا عميقًا لسلوكيات البيانات وتأثيرات نماذجك. أيضًا عندما تريد تدريب نماذج الذكاء الاصطناعي ستحتاج أن تقوم برفع المشروع إلى google colab وهو يعتمد على Jupyter Notebook، وبعد تعلمك اساسيات التعلم الآلي وعندما تأخذ نماذجك وقت كبير في عملية التدريب سوف تلجأ إلى استخدام google colab بفضل قدرة الحوسبة المجانية عن طريق كروت الشاشة GPU التي يوفرها والتي سوف تسرع عملية التدريب، لذلك أنصحك بالتعود عليه ثم بعدها استخدم ما تشاء
  17. السلام عليكم اي الفرق بين netbook وبين vscode واي الافضل في مجال تعلم الاله ؟
  18. كما وضح أستاذنا حمزة عباد: إذا كنت تتابع إحدى الدورات يرجى طرح السؤال أسفل الدرس. ولكن يبدو من لقطات الشاشة أنك استخدمت الأمر let x = new sqlite3.Database("workx1"); لاحظ أنك لم تضع امتدادًا في اسم قواعد البيانات وبالتالي فإن قواعد البيانات ستأخذ الامتداد .sqlite لذلك ابحث في المجلد عن قواعد البيانات بالاسم workx1.sqlite أو ضع امتدادًا للملف فيصير let x = new sqlite3.Database("workx1.db");
  19. يكفي الاحتفاظ بالتمارين المطلوب حلها أو المشاريع العملية التي تقوم بإنشاءها مع المدرب في كل مسار.ولكن من الجيد أن تقوم بالتطبيق مع المدرب في جميع التمارين ويمكنك الاحتفاظ بكل المشاريع والتمارين التي تطبقها أثناء الدروس مع المدرب للمراجعة عليها أحياناً. مع ذلك فإن المشاريع في نهاية كل مسار أو الدروس التي تكون تطبيقات عملية هو ما يكفي لتقديمه لذلك يجب عليك الإحتفاظ به ويفضل الإطلاع على الإجابات التالية .
  20. السلام عليكم قرأت في شروط التقدم للامتحان أنه يجب التطبيق العملي مع المدرب، والاحتفاظ بالمشاريع العملية الناتجة لإرسالها للمراجعة. هل يجب علي أن أحتفظ بكل المشاريع العملية التي طبقتها في الدرس مع المدرب؟ أم أحتفظ بحل التمارين الموجودة في الدرس فقط؟ مثلاً: في شرح لغة الاستعلام SQL ، يكون هناك العديد من التمارين التي يعمل عليها المدرب في الدرس وفي الأخير يعطينا تمارين لحلها. هل الإحتفاظ بحل التمارين كافي لتقدم للإمتحان ؟ شكراً
  21. شكراً لك على الشرح الأكثر من رائع يوجد لدي سؤال اخر اريد توضيح نقطة انه قاعدة البيانات ستكون في حالة تطوير مستمر و عندما قرأت عن القواعد البيانات العلائقيه ف هيا غير مناسبة لمثل هذا النوع من التطبيقات لانه ستسبب مشاكل على غرار قواعد البيانات الغير علائقيه ف كيف يمكنني من هده النقطه تحديد الأفضل بالنسبة لي!
  22. أنت لا تقوم بتحديث موضع obstacleY بشكل صحيح في دالة detectCollision، حيث تقوم بالتحقق هل العائق قد اصطدم باللاعب عن طريق التحقق مما إن كان الفرق المطلق بين موضع الـ x للاعب وموضع الـ x للعائق أقل من مجموع نصف عرض اللاعب ونصف حجم العائق، ولكنك لا تأخذ في الاعتبار موضع الـ y للعائق. فعند موضع الـ y للعائق أكبر من 1، تقوم بإعادة تعيين موضع العائق والتحقق من الاصطدام، لكن لا تقوم بتحديث موضع الـ y للعائق بشكل صحيح، بل فقط تقوم بتحديث موضع الـ x للعائق. لذا، تحتاج إلى تحديث موضع الـ y للعائق بشكل صحيح عن طريق طرح سرعة اللعبة من موضع الـ y للعائق في كل مرة يتم فيها تنشيط المؤقت. _timer = Timer.periodic(Duration(milliseconds: 50), (timer) { setState(() { obstacleY -= gameSpeed; // تحديث موضع الـ y للعائق if (obstacleY > 1) { if (detectCollision(playerX, 0.8, obstacleX, obstacleY)) { score++; gameSpeed *= 1.05; } else { lives--; if (lives <= 0) { timer.cancel(); _showGameOverDialog(); } } resetObstacle(); } }); }); أيضًا تحتاج إلى تحديث دالة detectCollision لتأخذ في الاعتبار موضع الـ y للعائق: bool detectCollision(double playerX, double playerY, double obstacleX, double obstacleY) { double playerWidth = 0.15; double playerHeight = 0.05; double obstacleSize = 0.05; return (playerX - obstacleX).abs() < (playerWidth / 2 + obstacleSize / 2) && (playerY - obstacleY).abs() < (playerHeight / 2 + obstacleSize / 2); } ومن المفترض الآن أن تنخفض القلوب عندما يصطدم العائق باللاعب.
  23. البارحة
  24. يجب ملاحظة شيء هام، وهو ليس الهدف من أكاديمية حسوب حل التمارين والاختبارات للطلاب فهذا ضد قواعد الأكاديمية. إنما الهدف هو تعليم المتدربين والطلاب واكتسابهم المهارات التي من خلالها يستطيعون حل المشاكل بأنفسهم. لذلك، أعطيكي بعض الملاحظات على الشيفرات المكتوبة، وعليك اكتشاف الخطأ وتصحيحه. أولاً: عند تعليق سطر (أي تهميشه) فإن السطر لا يتم تنفيذه ويتم اعتباره كأنه ملاحظة مكتوبة وليس شيفرت مطلوب تنفيذها، وبناء عليه يجب عليك البحث عن السطور المعلقة (التي قبلها //) وتفعيلها. ثانيًا: في الحلقة التكرارية for نحن نحدد البداية مثل i = 0 ونحدد الشرط الذي يضمن استمرار الحلقة التكرارية مثل i <= count ونحدد قيمة الزيادة عن طريق ++i، فإذا كتبنا الشرط بشكل خاطيء سيحدث خطأ إما الحلقة لن يتم تنفيذها، أو سيبدأ تنفيذها ولكنها لن تنتهي أبدًا، لذلك يجب عليكِ النظر في الشرط المكتوب وتصحيحه.
  1. عرض المزيد
×
×
  • أضف...