velody/docs/PROJECT_ENVIRONMENT_ARCHITECTURE.md
2026-05-24 20:53:42 +02:00

40 KiB

Velody

خطة المعمارية وبيئة المشروع

Phase 1

هذه الوثيقة تغطي فقط:

  • المعمارية الكاملة
  • خطة التنفيذ
  • هيكل المشروع
  • استراتيجيات التخزين والمزامنة
  • بيئة التطوير والتشغيل

ولا تبدأ بتنفيذ التطبيق الكامل.

0. قرارات تأسيسية

  • النظام خاص جدًا لمالك واحد فقط.
  • لا يوجد public multi-user auth في المرحلة الأولى.
  • يوجد حد أدنى من الحماية التقنية بين الأجهزة والخادم عبر device install token أو shared secret مخزن في Keychain بدون بناء نظام حسابات.
  • المصدر الحقيقي للمكتبة هو الخادم.
  • تطبيق macOS هو مصدر الإدخال ingest source للملفات.
  • تطبيق iPhone هو مستهلك للمكتبة مع تنزيل محلي كامل وتشغيل offline-first .
  • الخلفية الموصى بها هي modular monolith باستخدام NestJS وليس microservices في البداية.
  • التخزين الصوتي يكون على قرص VPS المحلي، وليس داخل PostgreSQL .
  • المزامنة بين الأجهزة تبنى على append-only change log بدل إعادة تحميل كامل المكتبة كل مرة.

1. نظرة عامة على النظام

الهدف العملي

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

النتيجة المعمارية المطلوبة

  1. المستخدم يضع ملفات MP3 داخل مجلد مراقب على macOS .
  2. تطبيق macOS يلتقط التغييرات، ينتظر استقرار الملف، يستخرج البيانات الوصفية، ثم يضع الملف في طابور رفع دائم.
  3. الخادم يستقبل الملف بشكل قابل للاستئناف، يتحقق من سلامته، يحفظه على القرص، ويسجل بياناته الوصفية في قاعدة البيانات.
  4. الخادم يضيف حدثًا جديدًا إلى سجل التغييرات.
  5. تطبيق iPhone يقرأ التغييرات الجديدة، ينزل الملفات المطلوبة في الخلفية، يتحقق من سلامتها، ويحفظها محليًا للتشغيل بدون إنترنت.

التوصية الأساسية

ابدأ بـ:

  • تطبيقين أصليين SwiftUI .
  • حزم مشتركة Swift Package للمنطق المشترك.
  • خادم Node.js + TypeScript + NestJS .
  • قاعدة PostgreSQL .
  • تخزين ملفات على قرص VPS .
  • واجهات REST API بإصدار versioned API من البداية.

2. مخطط نصي للنظام

                           +----------------------+
                           |   Watched Folder     |
                           |      on macOS        |
                           +----------+-----------+
                                      |
                                      v
                           +----------------------+
                           | FSEvents Watcher     |
                           | + Stability Check    |
                           +----------+-----------+
                                      |
                                      v
                           +----------------------+
                           | Local Upload Queue   |
                           | + Metadata Extract   |
                           | + SHA-256 Hashing    |
                           +----------+-----------+
                                      |
                                      v
                     +----------------------------------------+
                     | HTTPS REST API via Nginx               |
                     | music.diyaa.de                         |
                     +----------------+-----------------------+
                                      |
                                      v
                     +----------------------------------------+
                     | NestJS Backend                         |
                     | - Upload Sessions                      |
                     | - Library Service                      |
                     | - Sync Events                          |
                     | - Validation / Metadata                |
                     +-----------+----------------------------+
                                 | 
                   +-------------+-------------+
                   |                           |
                   v                           v
        +-----------------------+   +---------------------------+
        | PostgreSQL            |   | VPS File Storage          |
        | metadata + sync log   |   | audio/artwork/incoming    |
        +-----------+-----------+   +-------------+-------------+
                    |                               |
                    +---------------+---------------+
                                    |
                                    v
                         +--------------------------+
                         | Sync Changes Endpoint    |
                         +------------+-------------+
                                      |
                                      v
                         +--------------------------+
                         | iPhone Sync Engine       |
                         | BGTasks + URLSession     |
                         +------------+-------------+
                                      |
                                      v
                         +--------------------------+
                         | Local Offline Store      |
                         | + Core Data catalog      |
                         | + local audio files      |
                         +------------+-------------+
                                      |
                                      v
                         +--------------------------+
                         | AVPlayer / AVQueuePlayer |
                         | background playback      |
                         +--------------------------+

3. هيكل المشروع الموصى به

velody/
  apps/
    apple/
      Velody.xcworkspace
      VelodyMac/
        VelodyMacApp/
        VelodyMacTests/
      VelodyiPhone/
        VelodyiPhoneApp/
        VelodyiPhoneTests/

  packages/
    apple/
      VelodyDomain/
      VelodyNetworking/
      VelodyPersistence/
      VelodySync/
      VelodyPlayback/
      VelodyUtilities/

  backend/
    src/
      main.ts
      app.module.ts
      modules/
        health/
        config/
        devices/
        library/
        uploads/
        metadata/
        artwork/
        sync/
        storage/
      common/
        dto/
        pipes/
        filters/
        interceptors/
        utils/
      infrastructure/
        database/
        filesystem/
        logging/
    prisma/
      schema.prisma
      migrations/
    test/
      integration/
      e2e/
    Dockerfile
    package.json

  infra/
    docker/
      compose.local.yml
      compose.vps.yml
      env/
        backend.env.example
        postgres.env.example
    nginx/
      music.diyaa.de.conf
    scripts/
      deploy.sh
      backup-db.sh
      backup-storage.sh

  runtime/
    local-dropbox/
    storage/
    logs/

  docs/
    PROJECT_ENVIRONMENT_ARCHITECTURE.md

لماذا هذا الهيكل مناسب

  • يفصل بوضوح بين Apple apps و backend و infra .
  • يسمح بمشاركة المنطق الحقيقي فقط بين macOS و iPhone بدل إجبار واجهات متطابقة.
  • يبقي Docker وملفات التشغيل منفصلة عن كود التطبيق.
  • يسمح بإضافة widgets أو watchOS أو CarPlay لاحقًا بدون إعادة تنظيم جذرية.

4. المعمارية الخلفية الموصى بها

الأسلوب العام

التوصية هي:

modular monolith

باستخدام:

NestJS

داخل حاوية واحدة في البداية.

هذا الخيار يعطيك:

  • هيكلة واضحة
  • قابلية اختبار أعلى
  • سهولة إضافة وحدات مستقبلية
  • بساطة تشغيلية أكبر من microservices

الوحدات الأساسية

1. HealthModule

المسؤولية:

  • فحص الجاهزية
  • فحص الاتصال بقاعدة البيانات
  • فحص إمكانية الكتابة على التخزين

2. ConfigModule

المسؤولية:

  • تحميل environment variables
  • التحقق من القيم الإلزامية
  • فصل إعدادات dev / staging / production

3. DevicesModule

المسؤولية:

  • تسجيل الأجهزة
  • حفظ نوع الجهاز وإصداره وآخر نشاط
  • إدارة install token أو device secret

4. UploadsModule

المسؤولية:

  • إنشاء جلسات رفع resumable upload sessions
  • متابعة offset الحالي
  • استقبال الأجزاء chunks
  • إكمال الرفع أو رفضه

5. StorageModule

المسؤولية:

  • التعامل مع نظام الملفات
  • الكتابة إلى incoming ثم النقل إلى التخزين النهائي
  • إرجاع storage keys
  • عزل منطق التخزين بحيث يمكن لاحقًا استبداله بـ S3-compatible storage دون كسر طبقة الأعمال

6. MetadataModule

المسؤولية:

  • استخراج بيانات MP3 بعد الرفع
  • تطبيع الحقول normalization
  • استخراج الغلاف artwork إن وجد
  • التحقق من مدة الملف وصيغته وخصائصه التقنية

7. LibraryModule

المسؤولية:

  • إنشاء أو تحديث السجل المنطقي للأغنية
  • ربط السجل بالأصل الصوتي audio asset
  • إدارة حالة الحذف المنطقي soft delete
  • دعم الاستعلام عن المكتبة

8. ArtworkModule

المسؤولية:

  • تخزين الغلاف إن وجد
  • إعادة تقديمه للأجهزة
  • دعم caching headers و ETag

9. SyncModule

المسؤولية:

  • تسجيل أحداث التغيير
  • تقديم bootstrap أولي للمكتبة
  • تقديم incremental sync للأجهزة

ما الذي لا أنصح به الآن

  • لا Redis في البداية.
  • لا Kafka أو RabbitMQ .
  • لا تقسيم إلى عدة خدمات منفصلة.
  • لا GraphQL لأن المطلوب بسيط وواضح ويخدمه REST بشكل ممتاز.

5. تصميم قاعدة البيانات

المبدأ

افصل بين:

  • الكيان المنطقي للأغنية track
  • الملف الفعلي المخزن audio asset
  • سجل التغييرات sync events

هذا يمنع ربط كل شيء مباشرة بالملف نفسه، ويسهل دعم إعادة الترميز، الغلاف، أو تعديلات metadata لاحقًا.

الجداول الأساسية

devices

الغرض:

تعريف كل تثبيت شرعي للتطبيقات.

أعمدة مقترحة:

  • id
  • platform
  • device_name
  • app_version
  • install_token_hash
  • last_seen_at
  • created_at
  • updated_at

ملاحظات:

  • لا يمثل مستخدمًا.
  • يمثل جهازًا موثوقًا داخل المنظومة الخاصة.

tracks

الغرض:

التمثيل المنطقي للأغنية داخل المكتبة.

أعمدة مقترحة:

  • id
  • primary_audio_asset_id
  • artwork_asset_id
  • title
  • artist
  • album
  • album_artist
  • genre
  • disc_number
  • track_number
  • year
  • duration_ms
  • status
  • created_at
  • updated_at
  • deleted_at

ملاحظات:

  • هذا الجدول لا يخزن الملف نفسه.
  • هذا هو الكيان الذي ستشير إليه playlists و favorites و playback history لاحقًا.

audio_assets

الغرض:

وصف الملف الصوتي الفعلي المخزن على الخادم.

أعمدة مقترحة:

  • id
  • track_id
  • sha256
  • storage_key
  • original_filename
  • mime_type
  • file_extension
  • file_size_bytes
  • bit_rate_kbps
  • sample_rate_hz
  • channels
  • duration_ms
  • source_device_id
  • created_at

قيود مهمة:

  • sha256 يجب أن يكون unique .
  • storage_key يجب أن يكون unique .

artwork_assets

الغرض:

تخزين الغلاف المستخرج إن وجد.

أعمدة مقترحة:

  • id
  • sha256
  • storage_key
  • mime_type
  • width
  • height
  • file_size_bytes
  • created_at

upload_sessions

الغرض:

تمكين الرفع القابل للاستئناف.

أعمدة مقترحة:

  • id
  • device_id
  • expected_sha256
  • expected_size_bytes
  • received_bytes
  • temp_storage_path
  • status
  • expires_at
  • created_at
  • updated_at

library_events

الغرض:

تغذية المزامنة التزايدية incremental sync بين الخادم والأجهزة.

أعمدة مقترحة:

  • id من نوع bigserial
  • entity_type
  • entity_id
  • action
  • payload_version
  • created_at

فكرة الجدول:

  • أي إنشاء أو تحديث أو حذف يضيف صفًا جديدًا.
  • كل جهاز يحتفظ بآخر event id تمت معالجته.
  • هذا أبسط وأمتن من إعادة جلب كامل المكتبة كل مرة.

device_sync_cursors

الغرض:

معرفة آخر نقطة مزامنة لكل جهاز.

أعمدة مقترحة:

  • device_id
  • last_event_id
  • last_full_sync_at
  • updated_at

علاقات الجداول

devices 1 --- n upload_sessions
devices 1 --- 1 device_sync_cursors
tracks 1 --- n audio_assets
tracks 0 --- 1 artwork_assets
tracks 1 --- n library_events

جداول مستقبلية بدون كسر التصميم

  • playlists
  • playlist_items
  • favorites
  • playback_positions
  • recently_played
  • device_content_state

6. معمارية تخزين الملفات

مبدأ التخزين

لا تستخدم اسم الملف الأصلي كمفتاح تخزين فعلي.

استخدم:

content-addressed storage

يعتمد على:

SHA-256

تخطيط التخزين على

VPS

/srv/velody/
  data/
    incoming/
      <upload-session-id>.part
    quarantine/
      <upload-session-id>/
    library/
      audio/
        ab/
          cd/
            <sha256>.mp3
      artwork/
        ef/
          12/
            <sha256>.jpg
    temp/
  backups/
    postgres/
    storage-manifests/

لماذا التخزين بالهاش أفضل

  • يمنع مشاكل تكرار الأسماء.
  • يمنع مشاكل الأحرف الغريبة في أسماء الملفات.
  • يجعل التحقق من السلامة أسهل.
  • يسهل التبديل لاحقًا إلى تخزين سحابي.
  • يحسن تنظيم المجلدات عبر توزيع الملفات على مستويات فرعية.

استراتيجية التسمية

  • اسم الملف النهائي يعتمد على SHA-256 .
  • الامتداد يعتمد على النوع الفعلي المكتشف، وليس على الاسم المرسل فقط.
  • الاسم الأصلي يحفظ في قاعدة البيانات لأغراض العرض أو التتبع فقط.

استراتيجية الكاش

على الخادم

  • ملفات الصوت النهائية لا تعتبر cache بل هي أصل دائم.
  • الغلاف يمكن تقديمه مع ETag و Cache-Control مناسب.

على

macOS

  • مجلد محلي لطابور الرفع
  • قاعدة بيانات محلية لحالة الملفات
  • ملفات مؤقتة للتحليل فقط

على

iPhone

  • ملفات الصوت تحمل إلى Application Support
  • ملفات الغلاف تحفظ في مسار منفصل
  • الملفات المحلية تعامل كمكتبة offline library وليست مجرد streaming cache

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

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

7. معمارية المزامنة

أ. دورة رفع الملف من

macOS

  1. يصل حدث تغيير من مجلد المراقبة.
  2. يدخل الملف إلى مرحلة debounce وفحص الاستقرار.
  3. إذا بقي الحجم ووقت التعديل ثابتين لعدة ثوانٍ، يعتبر جاهزًا.
  4. يستخرج التطبيق بيانات أولية محليًا لتحسين تجربة المستخدم وسرعة التصنيف.
  5. يحسب SHA-256 عبر streaming وليس تحميل الملف كاملًا في الذاكرة.
  6. يستعلم عن حالة الملف عبر POST /api/v1/uploads/prepare .
  7. إذا كان الملف موجودًا مسبقًا بنفس الهاش: لا يعاد رفعه، بل يحدث الربط أو الحالة فقط.
  8. إذا لم يكن موجودًا: ينشئ الخادم upload session ويعيد upload id و next offset .
  9. يرفع العميل الملف على أجزاء متسلسلة.
  10. عند اكتمال الرفع: يطلب العميل finalize .
  11. الخادم يعيد حساب الهاش، ويتحقق من الحجم والصيغة.
  12. إذا نجح التحقق: ينقل الملف من incoming إلى library/audio داخل عملية نهائية آمنة.
  13. يحدث قاعدة البيانات داخل معاملة transaction واحدة.
  14. يضاف حدث جديد إلى library_events .

ب. دورة تنزيل الملف إلى

iPhone

  1. التطبيق يحتفظ بآخر event_id تمت معالجته.
  2. عند فتح التطبيق أو تشغيل background task يطلب: GET /api/v1/sync/changes?after=<event_id>
  3. يحصل على: أغاني جديدة، تحديثات، وحذوفات.
  4. ينشئ التطبيق مهام تنزيل عبر background URLSession .
  5. ينزل الملف إلى مسار مؤقت.
  6. بعد الاكتمال يتحقق من: الحجم، الهاش، وربما ETag .
  7. ينقل الملف نقلاً ذريًا atomic move إلى موقعه النهائي.
  8. يحدث السجل المحلي وقيمة sync cursor .
  9. يصبح الملف قابلًا للتشغيل بالكامل بدون اتصال.

ج. استراتيجية إعادة المحاولة

في

macOS

  • خزّن طابور الرفع في قاعدة محلية دائمة.
  • استخدم exponential backoff with jitter .
  • مثال عملي: 1 دقيقة، 5 دقائق، 15 دقيقة، ساعة، 6 ساعات.
  • أخطاء 5xx و network errors تعاد تلقائيًا.
  • أخطاء 4xx تعامل غالبًا كأخطاء دائمة وتظهر للمستخدم.

في

iPhone

  • التنزيلات الكبيرة تتم عبر background session لتفادي توقفها عند انتقال التطبيق للخلفية.
  • إذا انقطع التنزيل، يعتمد الاستئناف على دعم الخادم لطلبات Range وثبات ETag .

د. معالجة التعارضات

في Phase 1 لا يوجد تعارض متعدد المستخدمين عمليًا، لذلك نستخدم قواعد بسيطة وآمنة:

قاعدة الحقيقة

  • الخادم هو المصدر النهائي للمكتبة.
  • تطبيق iPhone لا يكتب إلى المكتبة في هذه المرحلة.

الملفات المكررة

  • نفس sha256 يعني نفس الأصل الصوتي.
  • لا يعاد رفع الملف ولا تنشأ نسخة جديدة افتراضيًا.

نفس المسار المحلي مع ملف جديد

إذا كان تطبيق macOS يربط ذلك المسار سابقًا مع track معين ثم تغيرت محتوياته:

  • يرفع الأصل الجديد
  • ثم يرسل existingTrackId في مرحلة الإنهاء
  • فيحدث السجل الحالي بدل إنشاء سجل جديد

هذا يحافظ على تحديث الأغنية نفسها عند تعديل الملف أو إعادة تصديره من نفس المصدر المحلي.

الحذف

  • يفضل في البداية استخدام soft delete على الخادم.
  • يرسل حدث حذف إلى الأجهزة.
  • تطبيق iPhone يزيل الملف المحلي عندما لا يكون قيد التشغيل النشط.

هـ. كشف التكرار

المرحلة الأولى:

  • المفتاح الأساسي هو full-file SHA-256 .

ترقية مستقبلية اختيارية:

  • إضافة audio fingerprint لتوحيد الملفات المعاد وسمها re-tagged files حتى لو تغيرت ID3 tags .

8. استراتيجية مراقبة المجلد في

macOS

الخيار الأفضل

استخدم:

FSEvents

عبر:

CoreServices

لماذا هذا هو الخيار الصحيح

  • مراقبة مجلدات بشكل recursive وبكلفة منخفضة.
  • مناسب للمجلدات التي تحتوي عددًا كبيرًا من الملفات.
  • أفضل من DispatchSource عندما يكون المطلوب مراقبة شجرة مجلدات كاملة.

الاستراتيجية العملية

  1. يختار المستخدم مجلد المكتبة عبر NSOpenPanel .
  2. يحفظ التطبيق إذن الوصول كمؤشر دائم: security-scoped bookmark إذا كان التطبيق sandboxed أو تريد أن يبقى جاهزًا للتوزيع الرسمي.
  3. يبدأ FSEventStream على جذر المجلد.
  4. الأحداث لا تؤدي مباشرة إلى رفع الملف، بل إلى إعادة فحص ذكية للمسار المتأثر.
  5. لا يعالج التطبيق الملف قبل التأكد من استقراره.
  6. عند تشغيل التطبيق أول مرة أو بعد انقطاع طويل، ينفذ full reconciliation scan للمجلد كاملًا.

اعتبارات الاعتمادية

  • تعامل مع حالات event coalescing و dropped events بإعادة مسح المسارات المتأثرة.
  • إذا ظهر علم مثل: MustScanSubDirs أو تغير جذر المجلد، نفذ مسحًا كاملًا.
  • تجاهل الملفات المخفية والمؤقتة مثل: .part و .tmp .
  • اعتمد على قاعدة محلية تسجل آخر hash / mtime / size معروف لكل ملف.

اعتبارات الأداء

  • لا تحسب الهاش لكل الملفات في كل حدث.
  • استخدم مرحلتين: فحص سريع mtime + size ثم hashing عند الحاجة فقط.
  • اجعل الرفع منفصلًا عن خيط watcher نفسه.
  • حد أقصى لعدد عمليات الرفع المتزامنة.

9. استراتيجية التخزين والعمل بدون إنترنت في

iPhone

التخزين المحلي

الموصى به:

  • قاعدة بيانات محلية Core Data للكتالوج والحالة
  • ملفات الصوت داخل Application Support
  • ملفات الغلاف داخل مسار منفصل

لماذا

Core Data هنا

  • ناضج جدًا
  • يدعم migrations بوضوح
  • يعمل جيدًا مع مهام الخلفية
  • مناسب أكثر من حلول أخف عندما تكون الأولوية للاستقرار طويل الأمد

أفضل الممارسات

  • لا تحفظ الملفات في Documents إلا إذا أردت إظهارها للمستخدم كملفات.
  • استبعد ملفات الموسيقى من النسخ الاحتياطي السحابي إذا كانت قابلة لإعادة التنزيل: isExcludedFromBackup .
  • لا تشغل من الملف المؤقت.
  • شغّل فقط من الملف النهائي المتحقق من سلامته.

مزامنة الخلفية

استخدم:

  • BGAppRefreshTask لفحص تغييرات خفيفة
  • BGProcessingTask لتنزيلات أكبر أو إعادة التحقق الدوري
  • URLSessionConfiguration.background(withIdentifier:) للتنزيلات الفعلية

معمارية التشغيل الصوتي

استخدم:

  • AVPlayer للتشغيل الفردي
  • AVQueuePlayer للطوابير المستقبلية
  • AVAudioSession بفئة .playback
  • MediaPlayer لدعم شاشة القفل وأوامر الوسائط

سلوك التشغيل بدون إنترنت

  • بمجرد اكتمال تنزيل الملف والتحقق منه، يصبح التشغيل مستقلًا عن الشبكة تمامًا.
  • الاحتفاظ بمسار الملف المحلي في السجل المحلي.
  • الاحتفاظ بآخر موضع تشغيل محليًا تمهيدًا لمزامنته لاحقًا.

10. معمارية

API

الاستراتيجية العامة

  • استخدم URI versioning من البداية.
  • المسار الأساسي: /api/v1
  • لا تخلط بين مسارات الإدارة، الرفع، والمزامنة.

بنية المسارات المقترحة

صحة النظام

  • GET /api/v1/health

الأجهزة

  • POST /api/v1/devices/register
  • POST /api/v1/devices/heartbeat

الرفع

  • POST /api/v1/uploads/prepare
  • GET /api/v1/uploads/:uploadId
  • PATCH /api/v1/uploads/:uploadId
  • POST /api/v1/uploads/:uploadId/finalize

المكتبة

  • GET /api/v1/tracks
  • GET /api/v1/tracks/:trackId
  • GET /api/v1/tracks/:trackId/file
  • GET /api/v1/artwork/:artworkId

المزامنة

  • GET /api/v1/sync/bootstrap
  • GET /api/v1/sync/changes

استراتيجية الرفع

لا أوصي في هذه المنظومة الخاصة بأن تبدأ بـ multipart single-shot upload فقط، لأن شرط الاستئناف مهم.

الأفضل:

  • إنشاء جلسة رفع
  • رفع متسلسل على أجزاء
  • استعلام عن current offset
  • إنهاء نهائي مع تحقق

استراتيجية metadata

  • التطبيق على macOS يمكنه إرسال metadata أولية.
  • الخادم يجب أن يعيد استخراجها أو يتحقق منها بعد اكتمال الرفع.
  • metadata النهائية المعتمدة يجب أن تكون من الخادم.

استجابة المزامنة

يجب أن تعيد واجهة المزامنة:

  • قائمة الأحداث الجديدة
  • السجلات الكاملة المتأثرة
  • الحذوفات كـ tombstones
  • آخر event_id جديد ليحفظه العميل

التوثيق

استخدم:

OpenAPI / Swagger

من البداية لتثبيت العقد API contract بين التطبيقات والخادم.

11. معمارية

Docker

محليًا

الخدمات المطلوبة:

  • api
  • postgres
  • nginx اختياري للمطابقة مع الإنتاج

في

VPS

التوصية العملية:

  • شغّل api و postgres عبر Docker Compose .
  • شغّل Nginx على المضيف host مباشرة لسهولة إدارة TLS و Let's Encrypt .

هذا أكثر بساطة تشغيلية من حشر كل شيء داخل الحاويات في هذه المرحلة.

الحاويات

api

  • صورة Node.js إنتاجية
  • يربط مجلد التخزين الدائم
  • يستمع داخليًا على منفذ خاص مثل 3000

postgres

  • حاوية PostgreSQL
  • بدون تعريض المنفذ للعالم الخارجي
  • فقط على الشبكة الداخلية

الأحجام

volumes

محليًا

  • حجم أو bind mount لبيانات PostgreSQL
  • مجلد محلي لاختبار تخزين MP3

إنتاجيًا

  • postgres_data
  • ربط: /srv/velody/data إلى داخل الحاوية

الشبكات

  • شبكة داخلية مثل: velody_internal
  • قاعدة البيانات غير مكشوفة خارجيًا
  • واجهة API لا تتعرض مباشرة للإنترنت
  • المضيف فقط هو من يمرر الطلبات عبر Nginx

استراتيجية النشر على

VPS

  1. بناء صورة الخادم
  2. رفع الصورة أو بناؤها على الخادم
  3. تشغيل docker compose up -d
  4. تمرير Nginx إلى 127.0.0.1:<api-port>
  5. تفعيل HTTPS عبر Let's Encrypt

ما الذي يجب إضافته تشغيليًا

  • نسخة احتياطية لقاعدة البيانات
  • نسخة احتياطية لتخزين الملفات
  • فحص healthcheck دوري
  • سجلات منظمة structured logs

12. المكتبات والأدوات الموصى بها

في تطبيقات

SwiftUI

الواجهة والحالة

  • SwiftUI
  • Observation
  • async/await

الشبكات

  • URLSession
  • URLSessionConfiguration.background

التخزين المحلي

  • Core Data

الصوت

  • AVFoundation
  • AVPlayer
  • AVQueuePlayer
  • MediaPlayer

مهام الخلفية

  • BackgroundTasks

مراقبة المجلد في

macOS

  • FSEvents عبر CoreServices

استخراج metadata والغلاف في

Apple side

  • AVFoundation
  • AVAsset.commonMetadata

في الخلفية

Backend

الإطار الأساسي

  • NestJS

الوصول إلى قاعدة البيانات

  • Prisma ORM

التحقق من الطلبات

  • ValidationPipe
  • class-validator

رفع الملفات

  • @nestjs/platform-express
  • multer من خلال تكامل NestJS

التحقق من نوع الملف

  • file-type

استخراج

MP3 metadata

  • music-metadata

التشفير والتحقق

  • مكتبة crypto المدمجة في Node.js

التوثيق

  • @nestjs/swagger

أدوات الاختبار

  • XCTest لتطبيقات Apple
  • Jest أو Vitest للخلفية
  • اختبارات e2e لواجهات API

13. اعتبارات الأمان

حتى بدون نظام حسابات، لا ينبغي ترك النظام مكشوفًا بلا حماية.

الحد الأدنى المطلوب

  • HTTPS only
  • عدم كشف PostgreSQL للعالم الخارجي
  • عدم كشف منفذ الخادم مباشرة إن أمكن
  • السماح بالوصول عبر Nginx فقط

حماية الوصول

  • استخدم install token أو shared secret لكل تطبيق مثبت
  • خزنه داخل Keychain على Apple platforms
  • خزن على الخادم فقط hash للرمز وليس الرمز الخام

حماية الرفع

  • تحقق من الحجم المتوقع
  • تحقق من SHA-256
  • تحقق من النوع الفعلي magic bytes وليس MIME المعلن فقط
  • ارفض أي ملف ليس MP3 في المرحلة الأولى
  • انقل الملفات المشبوهة إلى quarantine

حماية الملفات

  • لا تستخدم المسارات المرسلة من العميل مباشرة على نظام الملفات
  • لا تقدم الملفات من أسماء المستخدم الأصلية
  • لا تسمح path traversal

حماية البيانات

  • استخدم مستخدم قاعدة بيانات بصلاحيات محددة
  • افصل أسرار البيئة عن المستودع
  • فعّل نسخًا احتياطية دورية
  • راقب امتلاء القرص لأن التخزين الصوتي هو نقطة الفشل الأكثر واقعية

14. اعتبارات القابلية للتوسع

ما الذي يجعل هذا التصميم قابلًا للنمو

1. فصل

track عن asset

يسمح لاحقًا بإضافة:

  • صيغ متعددة
  • نسخ منخفضة الجودة
  • نسخ lossless
  • معاينات

2. وجود

library_events

يسمح لاحقًا بـ:

  • مزامنة أسرع
  • عدة أجهزة
  • متابعة continue listening
  • مزامنة موجهة بالتغييرات بدل الفحص الكامل

3. طبقة

StorageModule

تسمح لاحقًا بنقل التخزين من قرص VPS إلى تخزين كائني object storage بدون كسر API أو منطق الأعمال.

4. طبقة

DevicesModule

تعطيك طريقًا نظيفًا لإضافة:

  • أكثر من iPhone
  • أكثر من Mac
  • مزامنة متعددة الأجهزة

5. بنية

NestJS modules

تسمح بإضافة:

  • playlists
  • favorites
  • search
  • playback history
  • lyrics

كوحدات مستقلة نسبيًا.

ما الذي لا يحتاج توسعة مبكرة

  • لا تحتاج CDN الآن.
  • لا تحتاج message broker الآن.
  • لا تحتاج sharding أو distributed workers الآن.

15. خارطة طريق تنفيذية على مراحل

Phase 1

الهدف:

تثبيت المعمارية، البيئة، العقود، والهيكل.

يشمل:

  • إنشاء المستودع وهيكله
  • تجهيز Xcode workspace
  • تجهيز NestJS الأساسي
  • تجهيز Docker Compose
  • تصميم قاعدة البيانات
  • تعريف API contracts
  • توثيق كامل للمشروع

الناتج:

مرجع معماري واضح وقابل للتنفيذ.

Phase 2

الهدف:

بناء الأساس الخلفي القابل للاختبار.

يشمل:

  • إنشاء الخادم NestJS
  • ربط PostgreSQL
  • بناء Prisma schema
  • إنشاء وحدات: health, devices, uploads, storage, library, sync
  • تنفيذ رفع قابل للاستئناف
  • حفظ ملفات MP3 على القرص
  • بناء sync events

الناتج:

خادم عامل محليًا مع مسار رفع وتخزين حقيقي.

Phase 3

الهدف:

بناء تطبيق macOS كمصدر إدخال موثوق.

يشمل:

  • اختيار مجلد المراقبة
  • حفظ security-scoped bookmark
  • بناء FSEvents watcher
  • بناء طابور رفع دائم
  • استخراج metadata والغلاف
  • ربط الملف بـ existingTrackId عند التعديل من نفس المصدر المحلي

الناتج:

رفع تلقائي مستقر من macOS إلى الخادم.

Phase 4

الهدف:

بناء تطبيق iPhone بمكتبة محلية وتشغيل بدون إنترنت.

يشمل:

  • شاشة مكتبة محلية
  • مزامنة أولية bootstrap sync
  • مزامنة تزايدية
  • تنزيلات خلفية
  • تخزين محلي دائم
  • تشغيل AVPlayer في الخلفية

الناتج:

تطبيق iPhone يعمل كمشغل موسيقى محلي متصل بالخادم عند الحاجة فقط.

Phase 5

الهدف:

التقسية التشغيلية hardening .

يشمل:

  • تحسين الاستئناف
  • تحسين إعادة المحاولة
  • تسجيلات منظمة
  • مراقبة السعة
  • نسخ احتياطية
  • إدارة الحذف والمزامنة الدقيقة
  • اختبارات e2e

Phase 6

الهدف:

إضافة ميزات المنتج الأساسية.

يشمل:

  • playlists
  • favorites
  • search
  • shuffle / repeat
  • recently played
  • continue listening
  • مزامنة مواضع التشغيل

Phase 7

الهدف:

توسعة المنظومة بدون إعادة كتابة الأساس.

يشمل:

  • CarPlay
  • Apple Watch
  • أكثر من جهاز لكل نوع
  • مكتبات مشتركة مستقبلًا
  • نظام صلاحيات حقيقي إذا لزم لاحقًا

16. بيئة التطوير والتشغيل المقترحة

محليًا

تطبيقات

Apple

  • Xcode
  • Swift 5.9+
  • SwiftUI
  • iOS Simulator

الخلفية

  • Node.js 20+ ويفضل اعتماد إصدار LTS موحد في الفريق
  • npm أو pnpm
  • Docker Desktop
  • PostgreSQL داخل Docker

مسارات محلية مفيدة

runtime/local-dropbox/   -> مجلد إسقاط MP3 للتجربة
runtime/storage/         -> تخزين ملفات backend محليًا
runtime/logs/            -> سجلات التطوير

إنتاجيًا على

VPS

  • Debian 12
  • Docker Engine
  • Docker Compose plugin
  • PostgreSQL
  • حاوية backend
  • تخزين محلي على القرص
  • Nginx
  • Let's Encrypt
  • نطاق مثل: music.diyaa.de

متغيرات بيئة أساسية

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://...
STORAGE_ROOT=/srv/velody/data
PUBLIC_BASE_URL=https://music.diyaa.de
DEVICE_BOOTSTRAP_SECRET=...
MAX_UPLOAD_SIZE_BYTES=...

17. توصية ختامية

إذا كان الهدف هو بناء منظومة شخصية قوية، بسيطة، وتعيش طويلًا بدون إعادة كتابة لاحقًا، فهذه هي أفضل نقطة انطلاق عملية:

  • SwiftUI native apps
  • NestJS modular monolith
  • PostgreSQL للبيانات الوصفية
  • تخزين ملفات على قرص VPS
  • مزامنة مبنية على library_events
  • رفع قابل للاستئناف من macOS
  • تنزيلات خلفية وتخزين محلي كامل على iPhone

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

18. مراجع رسمية مفيدة