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

1762 lines
40 KiB
Markdown

# 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/
<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
#### مسارات محلية مفيدة
```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=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. مراجع رسمية مفيدة
- 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