نظام مراسلات مبني من الصفر (مو تعديل على مشروع hostaka القديم): محادثات خاصة + مجموعات، مع تشفير طرف-لطرف اختياري للمحادثات الخاصة، تفاعلات، تعديل/حذف، إيصالات قراءة، مؤشر كتابة، خصوصية المراسلة، والحظر.
المشروع القديم (hostaka) كان كل شيء بملفين ضخمين: server.js (4146 سطر) وdatabase.js
(1247 سطر)، فيهم كل شيء (منشورات، فيديو، AI، إدارة، مراسلات...) مكدّس ببعضه. هذا خلى أي
تعديل بسيط بنظام المراسلات محفوف بالمخاطر لأنه يمر بمنطق مرتبط بميزات تانية.
هذا المشروع مبني بمعمارية مفصولة:
src/
db/ اتصال قاعدة البيانات + تعريف الجداول (schema.js)
services/ منطق العمل الفعلي (استعلامات SQL) — لا يعرف شيء عن HTTP
routes/ طبقة HTTP الرقيقة — تستدعي services وترجع JSON، ما فيها منطق أعمال
middleware/ المصادقة (JWT) + معالج أخطاء مركزي واحد
public/ الواجهة (بدون build step — HTML/CSS/JS عادي)
كل جزء مسؤول عن شيء واحد فقط. لو صار خلل بميزة معيّنة (مثلاً التفاعلات)، المكان يلي فيه
المنطق محدود بملف واحد صغير (messages.service.js أو groups.service.js)، مو مبعثر
بملف 4000 سطر.
npm install
cp .env.example .env
# عدّل .env: حط رابط قاعدة بيانات Turso (أو استخدم TURSO_DATABASE_URL=file:./local.db للتجربة المحلية)
npm startيفتح على http://localhost:3000 — صفحة الدخول أول شيء تظهر (/login.html).
المشروع Express عادي (app.listen())، وهذا ما يشتغل مباشرة على Vercel لأنه بيئة
serverless (كل طلب ممكن يوصل لعملية جديدة تماماً، ما فيه سيرفر مستمر شغّال بالخلفية).
لهاي السبب مضاف:
src/app.js— يبني تطبيق Express بدونapp.listen()، قابل لإعادة الاستخدام.server.js— نقطة التشغيل المحلية فقط (npm start)، تستدعيapp.listen().api/index.js— نقطة الدخول لـ Vercel، تصدّر دالة(req, res) => ...بدل سيرفر مستمر، وتتأكد قاعدة البيانات جاهزة (ensureSchemaReady) قبل أي طلب — مهم لأن كل "cold start" بالسيرفرلس يشغّل الملف من جديد.vercel.json— يوجّه كل/api/*لملفapi/index.js؛ ملفاتpublic/تُخدَّم مباشرة من Vercel (static hosting) بدون المرور بالدالة إطلاقاً.
خطوات النشر:
-
اربط المستودع بمشروع Vercel (زي ما سويت).
-
الأهم: من إعدادات المشروع على Vercel → Settings → Environment Variables، ضيف:
TURSO_DATABASE_URLTURSO_AUTH_TOKENJWT_SECRET(سلسلة عشوائية طويلة)
ملف
.envالمحلي ما ينرفع لـ Vercel أبداً (وما لازم يكون بالمستودع أصلاً) — لازم تضبط المتغيرات يدوياً من لوحة Vercel. هذا على الأغلب سبب الكراش (FUNCTION_INVOCATION_FAILED) يلي شفته بالمشروع القديم — لو الدالة ما تلاقيTURSO_DATABASE_URL/TURSO_AUTH_TOKENصحيحة، القديم كان يطيح بشكل كامل. النسخة الجديدة بترجع رسالة خطأ واضحة (500 مع تفاصيل) بدل ما تطيح بصمت — جرّبتها فعلياً بمفاتيح خاطئة ورجعت رسالة مفهومة. -
بعد الإعداد، أعد النشر (Redeploy) — أو أي push جديد يكفي.
نفس نوع قاعدة البيانات القديمة (libSQL / Turso). يدعم الاتصال بنفس قاعدة بيانات
مشروع hostaka القديم مباشرة — عند أول تشغيل، الكود يتحقق تلقائياً من الأعمدة
الناقصة (يلي يحتاجها هذا المشروع بس ما كانت موجودة بالجداول القديمة) ويضيفها
بأمان (ALTER TABLE ... ADD COLUMN) بدون ما يلمس أي بيانات موجودة — الحسابات
وكلمات المرور (bcrypt، نفس الطريقة بالضبط) والرسائل والمجموعات القديمة تبقى كما هي.
جرّبت هذا فعلياً بمحاكاة قاعدة بيانات بنفس الـ schema القديم بالضبط + حساب حقيقي،
وأكدت إن تسجيل الدخول والمراسلة يشتغلوا فوراً بعد أول تشغيل، وإن التشغيل المتكرر
(زي ما بيصير بكل "cold start" على Vercel) آمن ولا يكرر الإضافة ولا يطيح.
الجداول التالية متطابقة تماماً مع القديم (صفر تغيير): message_reactions,
groups, group_messages, group_message_reactions, follows, blocks.
الجداول التالية فيها أعمدة إضافية جديدة بيضيفها المشروع تلقائياً لو ناقصة:
| جدول | أعمدة جديدة تُضاف تلقائياً |
|---|---|
users |
public_key, message_privacy, read_receipts |
messages |
reply_to, encrypted, iv |
group_members |
last_read_at |
جداول جديدة بالكامل (ما كانت موجودة بالقديم، تُنشأ فارغة): dm_nicknames,
typing_status, key_backups.
| جدول | الغرض |
|---|---|
users |
الحسابات (نفس حسابات hostaka القديمة لو وصّلت نفس القاعدة) |
messages |
رسائل خاصة (1↔1) |
message_reactions |
تفاعلات الرسائل الخاصة |
dm_nicknames |
كنية خاصة تراها أنت فقط لشخص معيّن |
typing_status |
مؤشر "يكتب الآن" (خاص + مجموعات) |
groups / group_members |
المجموعات وأعضاؤها وأدوارهم |
group_messages / group_message_reactions |
رسائل المجموعات وتفاعلاتها |
key_backups |
نسخة احتياطية من مفتاح التشفير محمية برمز PIN |
blocks / follows |
الحظر وخصوصية المراسلة |
يشمل المحادثات الخاصة فقط (المجموعات تحتاج بنية تشفير مختلفة — تشفير جماعي/توزيع مفاتيح — وهذا خارج نطاق هذا المشروع). التصميم:
- كل حساب له مفتاح ECDH (P-256) واحد، يتولّد بالمتصفح ويُخزّن بـ IndexedDB.
- المفتاح العام يُرفع للسيرفر (
/api/keys/public)؛ المفتاح الخاص لا يغادر جهازك أبداً بصيغة غير مشفّرة. - لكل محادثة، الطرفان يشتقّان نفس مفتاح AES-256-GCM عبر ECDH — السيرفر يرى نص مشفّر فقط.
- نسخة احتياطية بـ PIN: عشان ما تفقد إمكانية قراءة رسائلك القديمة لو غيّرت جهاز/متصفح، فيه خيار تخزين نسخة من المفتاح الخاص مشفّرة برمز PIN من 6 أرقام (PBKDF2 بـ 600 ألف تكرار). السيرفر ما يشوف رمز الـ PIN أبداً، بس يتحقق من "verifier" ويقفل الحساب مؤقتاً بعد 5 محاولات خاطئة.
- حدود صادقة: هذا تصميم تشفير ثابت لكل زوج محادثة (مو forward secrecy متل Signal)، ورمز PIN من 6 أرقام مهما كان الـ KDF بطيء يبقى عرضة نظرياً لهجوم برute-force حاسوبي ضخم لو انسرقت قاعدة البيانات كاملة. هذا قيد معروف ومقبول لأنظمة PIN القصيرة، مو خلل بالتصميم.
- معالج أخطاء مركزي واحد (
middleware/errorHandler.js) بدل try/catch متكرر بكل راوت ينسى أحياناً — أي استثناء غير متوقع يوصل هناك بدل ما يعلّق الطلب أو يطيح السيرفر. - مسارات API مصمّمة لتفادي تعارض الترتيب: مثلاً
/api/keys/backup/statusو/api/keys/public/:usernameبادئات منفصلة تماماً، بدل خلط مسار ثابت مع مسار بباراميتر بنفس المستوى (نمط بيسبب أخطاء "تبين بسيطة بس ما تنحل" لما ترتيب تعريف الراوتات يهم). - كل استعلام SQL معامل (parameterized) — بدون استثناء، بدون تركيب SQL بالنص.
- محادثات خاصة: إرسال/تعديل/حذف، صور، ردود على رسالة، تفاعلات، إيصالات قراءة، عدّاد غير مقروء
- كنية خاصة لكل محادثة، حذف محادثة كاملة
- خصوصية المراسلة (الجميع / متابعيني فقط / لا أحد) + حظر
- مجموعات: إنشاء/تعديل/حذف، أعضاء وأدوار (مالك/مشرف/عضو)، كنية داخل المجموعة
- رسائل مجموعات: إرسال/تعديل/حذف/تفاعلات، وسائط المجموعة
- مؤشر "يكتب الآن" لكل من الخاص والمجموعات
- تشفير طرف-لطرف اختياري للمحادثات الخاصة + نسخة احتياطية بـ PIN
احذف تعريف جدول users من src/db/schema.js (خله يشير لجدولك الموجود بنفس أسماء الأعمدة
المستخدمة: id, username, display_name, avatar, message_privacy, read_receipts)، واحذف
src/routes/auth.routes.js واستخدم نظام المصادقة عندك، بشرط يحط req.user.id بنفس الشكل
يلي يتوقعه middleware/auth.js.