1762 lines
40 KiB
Markdown
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
|