尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证سوريا عن قرب | Syria Up Close — independent travel guide (Next.js + Payload)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗دليل سفر مستقل عن سوريا، بالعربية أولاً ثم الإنجليزية، مع لوحة تحرير وصفحة خفيفة للمراسلين على الأرض.
المواصفات المعتمدة في SPEC.md، والمهام في PLAN.md، وعقد الجودة في CONSTRAINTS.md.
/admin./correspondent، وحجمها أقل من 150KB على الهاتف.S3_BUCKET، أو القرص المحلي (MEDIA_DIR)..nvmrc).npm run db يشغّل قاعدة مضمّنة (embedded-postgres).npm run fonts:subset). ملفات الخط الناتجة محفوظة في المستودع.npm ci
cp .env.example .env
املأ PAYLOAD_SECRET وحسابات الـ seed في .env، ثم شغّل القاعدة في نافذة طرفية مستقلة واتركها تعمل:
npm run db
وفي نافذة أخرى:
npm run migrate
npm run seed
npm run dev
/en.أي تغيير في نموذج البيانات يحتاج migration تُحفظ في المستودع:
npm run migrate:create -- اسم-التغيير
| الأمر | ماذا يفحص | متى |
|---|---|---|
npm run check:fast | الأنواع وlint والحارس (لا suppressions ولا اختبارات متخطّاة) | أثناء العمل |
npm run check:task | ما سبق، واختبارات الوحدة مع التغطية | قبل كل commit |
npm run check:full | ما سبق، واختبارات التكامل على Postgres، والبناء، وPlaywright (بما فيها axe وميزانية 150KB) | عند نقاط التحقق |
npm run lhci | Lighthouse على / و/cities/damascus و/correspondent/login | قبل الإطلاق |
لا تُخفَّض أي عتبة في CONSTRAINTS.md لتمرير تغيير.
الصورة تُبنى دون قاعدة بيانات. عند الإقلاع تجري ثلاث خطوات:
payload migrate).scripts/docker/start.mjs).لذلك يعمل البناء على أي منصة، حتى التي لا تصل إلى القاعدة أثناء البناء.
على الخادم، وفيه Docker مع Compose:
cp .env.example .env
املأ في .env:
PAYLOAD_SECRET: سلسلة عشوائية طويلة، مثلاً openssl rand -hex 32.NEXT_PUBLIC_SITE_URL: رابط الموقع النهائي بلا / في آخره. يُثبَّت داخل الصورة، فأعد البناء إن غيّرته.POSTGRES_PASSWORD.ثم:
docker compose up -d --build
أول مرة فقط، أدخل المحتوى الأولي ثم حدّث الصفحات لتظهر فوراً:
docker compose exec app npm run seed
docker compose exec app npm run revalidate
pgdata، والصور في volume media، وتبقيان بعد إعادة البناء.APP_PORT (افتراضياً 3000). ضع أمامه خادماً عكسياً بشهادة HTTPS، مثل Caddy أو Nginx.docker compose up -d --build لاحق يطبّق أي migration جديدة تلقائياً عند الإقلاع.حالة التحقق: Docker غير مثبت على جهاز التطوير، لذلك لم تُبنَ الصورة فعلياً بعد. ما جُرّب حتى الآن، على Windows دون Docker:
- مرحلة البناء نفسها على نسخة مطابقة لسياق Docker.
- الإقلاع الكامل على قاعدة جديدة: migrate، ثم seed، ثم
start.mjs. أظهر المحتوى الحقيقي بعد التحديث.يُختبر
Dockerfileكاملاً عبر بناء منصة النشر بعد اختيارها.
الموقع مستضاف مجاناً على ثلاث خدمات:
abdullah-98df/syria-up-close، في منطقة fra1.syria-up-close-prod (patient-wildflower-66185499) في aws-eu-central-1، الفرع production، Postgres 18.syria-up-close-blob (fra1) المربوط بالمشروع. أضاف Vercel المتغير BLOB_READ_WRITE_TOKEN إلى Production وPreview تلقائياً، فلا مفاتيح تُنسخ يدوياً.كل نشر على main يذهب إلى الإنتاج، وأي فرع آخر يصبح Preview. أمر البناء (npm run vercel-build في vercel.json) ينفّذ:
إن فشلت مهاجرة فشل النشر كله، ويبقى الإصدار السابق يعمل.
يختار الكود مكان التخزين من المتغيرات وحدها، دون تعديل كود (src/lib/storage.ts):
| الشرط | التخزين | أين يُستخدم |
|---|---|---|
BLOB_READ_WRITE_TOKEN موجود | Vercel Blob | الإنتاج والمعاينة |
S3_BUCKET موجود، ولا يوجد رمز Blob | S3 أو Cloudflare R2 | للانتقال لاحقاً |
| لا هذا ولا ذاك | القرص المحلي (MEDIA_DIR) | التطوير وصورة Docker |
في الكود (مفعّل):
preview/.فلا تستطيع المعاينة أن تكتب فوق ملف للإنتاج أو تحذفه.
الفصل الكامل: منجز (2026-10-19). كل مخزن مربوط ببيئة واحدة فقط:
| المخزن | البيئة |
|---|---|
syria-up-close-blob | Production فقط |
syria-up-close-blob-preview (fra1، عام) | Preview فقط |
وقد تحققنا من ذلك بـ vercel env ls: لكل بيئة BLOB_READ_WRITE_TOKEN وBLOB_STORE_ID مختلفان.
لإعادة الإعداد من الصفر، الخطوات هي:
syria-up-close-blob-preview.fra1).syria-up-close:
BLOB_READ_WRITE_TOKEN).syria-up-close-blob ← Projects ← بجانب syria-up-close، اضغط ⋯ ← Edit:
BLOB_READ_WRITE_TOKEN مرتين:
syria-up-close-blob؛syria-up-close-blob-preview.حسب أسعار Vercel ووثائق Blob، اطّلاع 2026-10-18:
| المورد | الحد الشهري |
|---|---|
| التخزين | 1GB |
| نقل البيانات | 10GB |
| العمليات المتقدمة (الرفع) | 2,000 |
| العمليات البسيطة (القراءة دون cache) | 10,000 |
thumb وcard وhero). فالحد العملي نحو 500 صورة في الشهر، ونحو 1,500 صورة في التخزين كله (حوالي 650KB للصورة بأحجامها).S3_* وحذف ربط Blob، دون تعديل كود. أو نرقّي إلى Pro.syria-up-close-prod في AWS Frankfurt (aws-eu-central-1).
main، أعد تسميته إلى production.production ← Computes ← Edit، اجعل الحجم الأدنى والأعلى 0.25 CU. التوقف بعد 5 دقائق خمول ثابت في الخطة المجانية.preview، واجعل حجمه 0.25 CU أيضاً.لكل فرع سلسلتا اتصال، من Connect أو من neonctl connection-string:
--pooled): هذه DATABASE_URL.DATABASE_URL_UNPOOLED.يجب أن تكون السلسلتان للفرع نفسه، وإلا رفض البناء أن يعمل.
من Project ← Settings ← Environment Variables، أو عبر vercel env add. فعّل Sensitive للأسرار.
| المتغير | Production | Preview | من أين |
|---|---|---|---|
DATABASE_URL 🔒 | فرع production في syria-up-close-prod، مع pooling | فرع preview في مشروع أوهايو، مع pooling | Neon |
DATABASE_URL_UNPOOLED 🔒 | الفرع نفسه، بدون pooling | الفرع نفسه، بدون pooling | Neon |
PAYLOAD_SECRET 🔒 | قيمة عشوائية: openssl rand -hex 32 | قيمة مختلفة | تولّدها أنت |
NEXT_PUBLIC_SITE_URL | رابط الإنتاج، ثم النطاق لاحقاً | لا تضعه: المعاينة تستخدم عنوانها تلقائياً | Vercel ← Domains |
BLOB_READ_WRITE_TOKEN 🔒 | يضيفه Vercel تلقائياً | يضيفه Vercel تلقائياً | ربط مخزن Blob |
S3_* وR2_PUBLIC_URL | لا تضعها ما دام Blob مستخدماً | — | للانتقال إلى R2 لاحقاً |
INDEXNOW_KEY | قيمة عشوائية: openssl rand -hex 16 (8–128 حرفاً أو رقماً أو شرطة) | لا تضعه | تولّدها أنت. يُخدم على /indexnow-key.txt، ولا يُرسل شيء لـ IndexNow إلا من Production |
إعدادات المشروع في Vercel:
vercel.json، فلا تغيّرهما.لا توضع SEED_* في Vercel؛ لا تلزم إلا للـ seed من الجهاز.
هذا الدعم باقٍ في الكود. إن احتجناه:
PUT وGET وبالترويسات content-type وif-none-match من رابط الموقع.S3_BUCKET وS3_ENDPOINT (https://<ACCOUNT_ID>.r2.cloudflarestorage.com) وS3_REGION=auto والمفتاحين في Vercel.BLOB_READ_WRITE_TOKEN، ثم أعد النشر.R2_PUBLIC_URL.الصور القديمة في Blob لا تُنقل تلقائياً.
أنشئ على جهازك ملف .env.production.local، وهو مستثنى من git، وفيه:
DATABASE_URL وDATABASE_URL_UNPOOLED لفرع production.PAYLOAD_SECRET نفسه الموجود في Vercel.NEXT_PUBLIC_SITE_URL.SEED_* بكلمات مرور قوية.لا يحوي هذا الملف أي S3_*، لأن الإنتاج على Blob.
رمز Blob يُسحب إلى ملف منفصل، مستثنى من git أيضاً، دون طباعته:
npx vercel env pull .env.blob.local --environment=production
(ثم يُترك فيه BLOB_READ_WRITE_TOKEN وحده.)
ثم:
npm run seed:remote -- .env.production.local .env.blob.local
السكربت لا يطبع أي قيمة، ويرفض العمل إذا:
بعد الـ seed يحدّث صفحات الموقع المنشور. القياس بعدها:
npm run lhci -- https://<اسم-المشروع>.vercel.app
خطة Vercel Hobby للاستخدام الشخصي وغير التجاري فقط. المشروع غير تجاري:
إن بدأ أي دخل أو عمولة أو إعلان مرتبط بالموقع: ترقية فورية إلى Vercel Pro أو الانتقال إلى Fly.io عبر Dockerfile.
حسب وثائق Vercel (آخر تحديث 2026-08-28):
فقاعدة واحدة تغطي مسارات الدخول كلها. هذا يضاف إلى قفل Payload لكل حساب بعد 5 محاولات خاطئة مدة 15 دقيقة.
syria-up-close ← Firewall في الشريط الجانبي.login-rate-limit.POST.^/(api/users/(login|forgot-password|unlock)|correspondent/login(/submit)?)$.60s.10.ملاحظات:
PATCH /v1/security/firewall/config (rules.update)؛ أما PUT فيرفضه Vercel على Hobby («Rate limiting is not available for this plan»)./correspondent/login/submit (مسار مستقل، لا server action)، لأن الـ server action يمكن استدعاؤه من أي صفحة فيتجاوز القاعدة./correspondent أو /admin) تُحفظ باسم عشوائي (UUID)، لا باسم ملف الهاتف، فلا يمكن تخمين رابطها قبل النشر./admin بحساب مدير./correspondent/login وكلمة المرور المؤقتة عبر قناة خاصة.أو بأمر واحد (يطبّق القواعد تلقائياً):
npm run correspondent:create -- --email <بريد مستعار> --name "<الاسم الحقيقي>" --city <slug المدينة> --env .env.production.local
accounts/ (مستثنى من git) دون طباعتها. أرسلها للمراسل عبر قناة خاصة، ثم احذف الملف.--env يعمل على القاعدة المحلية.c-7f3k@نطاقك). بذلك لا يفيد أي تسرّب لوجود البريد./admin (المستخدمون ← الحساب ← كلمة السر الجديدة)، ثم يرسلها عبر قناة خاصة.صفحة /correspondent مصمّمة للمراسل على الهاتف. يستطيع فتح /admin أيضاً، لكن صلاحياته هناك وهنا محصورة في مدنه:
لإيقاف مراسل: احذف مدنه من المدن المعيّنة، أو احذف حسابه. كل تغيير يُسجَّل في سجل التدقيق.
بعد 5 محاولات دخول خاطئة يتوقف دخول الحساب ربع ساعة. الرسالة عندها هي نفسها رسالة كلمة السر الخاطئة، حتى لا تكشف أن البريد مسجّل. فك القفل قبل ذلك للمدير وحده.
النسخ منطقي: كل جدول يُحفظ صفوفاً مع قائمة الـ migrations المطبّقة. السبب أن pg_dump المحلي (Postgres 17) لا يقبل خادم Neon (Postgres 18). المنطق في src/data/backup.ts.
نسخة من قاعدة الإنتاج، بملف .env.production.local (الاتصال المباشر):
npm run backup -- .env.production.local
تُحفظ في backups/ (مستثناة من git) مشفّرة فقط (.json.gz.gpg، gpg بتشفير AES-256)، ولا يُكتب أي نص صريح على القرص. السبب أن فيها كل الصفوف، ومنها بصمات كلمات السر والأسماء الحقيقية.
.env.backup.local (مستثنى من git). يولّدها أول نسخ تلقائياً دون طباعتها. احفظ نسخة منها في مدير كلمات المرور: بدونها لا يمكن قراءة أي نسخة.GPG.استعادة نسخة في قاعدة محلية:
npm run restore -- backups/<الملف>.json.gz.gpg <ملف env فيه DATABASE_URL للهدف>
يفك التشفير في الذاكرة بكلمة المرور من .env.backup.local.
خطوات الاستعادة:
ترفض أي هدف غير محلي ما لم يُمرَّر --allow-remote صراحة، لأنها تمسح كل ما فيه.
تجربة الاستعادة (نسخ، ثم استعادة في Postgres محلي مؤقت، ثم مقارنة). تُكرَّر بعد كل تغيير في المخطط، ومرة في الشهر على الأقل:
npm run backup:drill -- .env.production.local
التجربة تستعيد من الملف المشفّر نفسه بعد فك تشفيره، لا من الذاكرة، فتُثبت أن النسخة على القرص قابلة للقراءة.
آخر تجربة على الإنتاج: 2026-10-19 (مشفّرة). 42 جدولاً، 13 منها فيها بيانات، وتطابقت أعداد كل الجداول.
ما لا يشمله النسخ: ملفات الصور على Vercel Blob. خط الدفاع الثاني للقاعدة هو استعادة Neon اللحظية خلال 6 ساعات (الخطة المجانية).
IBM Plex Sans Arabic (رخصة OFL) مستضاف ذاتياً ومقصوص إلى:
ملفات الخط في public/fonts، ونسخ صور المشاركة في src/og/fonts. لإعادة القصّ:
npm run fonts:subset
الاختبار tests/unit/font-coverage.test.ts يفشل إن استُخدم في النصوص حرف غير موجود في الخط المقصوص.
docs/source/ مرجعية ولا تُعدَّل..env مستثنى من git ومن صورة Docker.