# 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. مخطط نصي للنظام ```text +----------------------+ | 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. هيكل المشروع الموصى به ```text 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` ### علاقات الجداول ```text 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 ```text /srv/velody/ data/ incoming/ .part quarantine/ / library/ audio/ ab/ cd/ .mp3 artwork/ ef/ 12/ .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=` 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:` 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 #### مسارات محلية مفيدة ```text 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` ### متغيرات بيئة أساسية ```text NODE_ENV=production PORT=3007 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. مراجع رسمية مفيدة - Apple `FSEvents` https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html - Apple `BackgroundTasks` https://developer.apple.com/documentation/uikit/using-background-tasks-to-update-your-app - Apple `URLSession background transfers` https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1407496-background - Apple `AVAudioSession` https://developer.apple.com/documentation/avfaudio/avaudiosession - Apple `AVPlayer` https://developer.apple.com/documentation/avfoundation/avplayer - Apple `AVQueuePlayer` https://developer.apple.com/documentation/avfoundation/avqueueplayer - Apple `Core Data` https://developer.apple.com/documentation/coredata - Apple `macOS App Sandbox and security-scoped bookmarks` https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox - NestJS `Modules` https://docs.nestjs.com/modules - NestJS `Validation` https://docs.nestjs.com/techniques/validation - NestJS `File upload` https://docs.nestjs.com/techniques/file-upload - NestJS `Versioning` https://docs.nestjs.com/techniques/versioning - NestJS `OpenAPI` https://docs.nestjs.com/openapi/introduction - Prisma https://www.prisma.io/docs - Docker Compose https://docs.docker.com/compose/ - Docker networks https://docs.docker.com/compose/how-tos/networking/ - Docker volumes https://docs.docker.com/reference/compose-file/volumes/ - PostgreSQL https://www.postgresql.org/docs/ - PostgreSQL `pgcrypto` https://www.postgresql.org/docs/16/pgcrypto.html - `music-metadata` https://github.com/Borewit/music-metadata - `file-type` https://github.com/sindresorhus/file-type