B4 نقاط التوقف: stops jsonb؛ المسار والتسعير يمرّان بالنقاط بالترتيب (أصل → توقف₁ → … → وجهة) بجمع أرجل المسار. بلا توقفات = رِجل واحدة كالسابق. B5 الحجز المسبق: scheduled_at + حالة scheduled جديدة (لا تدخل Redis ولا تُوزَّع الآن). ScheduledTripsSweeper يعمل داخل الـAPI لا الـworker — لأن الإطلاق يبثّ عروضاً عبر RealtimeGateway الموجود في الـAPI فقط. آمن مع عدة نسخ: releaseDueScheduled يستعمل UPDATE شرطياً (scheduled→searching) فلا تُطلَق رحلة مرتين. فهرس جزئي على scheduled المستحقة. B8 مطابقة الوجهة: is_destination_match + destination_match_discount + destination_match_discount_pct في التعرفة، يُطبَّق في settleFare. الخصم على الراكب والسائق يقبض المخفَّض كاملاً (يربح رحلة في طريقه أصلاً). مؤجَّل: محرّك المطابقة الذي يضبط العلم (وضع وجهة السائق). هجرة: TripStopsSchedulingMatch. حالة trip جديدة: scheduled (تنتقل إلى searching أو cancelled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
4.8 KiB
TypeScript
117 lines
4.8 KiB
TypeScript
/**
|
|
* إعداد مركزي يقرأ متغيرات البيئة مع افتراضات العزل (راجع docs/14).
|
|
*/
|
|
export default () => ({
|
|
env: process.env.NODE_ENV ?? 'development',
|
|
apiPort: parseInt(process.env.API_PORT ?? '4010', 10),
|
|
|
|
db: {
|
|
host: process.env.DB_HOST ?? 'postgres',
|
|
port: parseInt(process.env.DB_PORT ?? '5432', 10),
|
|
name: process.env.DB_NAME ?? 'tripz',
|
|
user: process.env.DB_USER ?? 'tripz',
|
|
password: process.env.DB_PASSWORD ?? 'change_me_strong',
|
|
// بادئة الجداول لعزل Tripz عن باقي البرامج على نفس السيرفر
|
|
tablePrefix: process.env.DB_TABLE_PREFIX ?? 'tripz_',
|
|
synchronize: process.env.DB_SYNC === 'true',
|
|
},
|
|
|
|
redis: {
|
|
host: process.env.REDIS_HOST ?? 'redis',
|
|
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
|
|
// DB رقم غير الافتراضي 0 لعزل Tripz عن باقي البرامج
|
|
db: parseInt(process.env.REDIS_DB ?? '3', 10),
|
|
keyPrefix: process.env.REDIS_KEY_PREFIX ?? 'tripz:',
|
|
},
|
|
|
|
queue: {
|
|
prefix: process.env.QUEUE_PREFIX ?? 'tripz_',
|
|
},
|
|
|
|
jwt: {
|
|
secret: process.env.JWT_SECRET ?? 'change_me_jwt_secret',
|
|
expires: process.env.JWT_EXPIRES ?? '15m',
|
|
refreshExpires: process.env.JWT_REFRESH_EXPIRES ?? '30d',
|
|
},
|
|
|
|
auth: {
|
|
// وضع تطوير: رمز OTP ثابت 0000 يُطبع باللوغ بلا مزوّد SMS (راجع docs/07).
|
|
// للإنتاج: OTP_DEV_MODE=false + ربط محوّل SMS في P2.
|
|
otpDevMode: process.env.OTP_DEV_MODE !== 'false',
|
|
otpTtl: parseInt(process.env.OTP_TTL ?? '300', 10),
|
|
otpLength: parseInt(process.env.OTP_LENGTH ?? '4', 10),
|
|
// ربط الجلسة بالجهاز (docs/17 — D2). مطفأ حتى يرسل فلاتر x-device-id؛
|
|
// تفعيله قبل ذلك يقطع كل طلب مصادَق (نفس نمط PAYMENTS_REQUIRE_SIGNATURE).
|
|
requireDeviceBinding: process.env.AUTH_REQUIRE_DEVICE_BINDING === 'true',
|
|
},
|
|
|
|
maps: {
|
|
tilesUrl: process.env.MAPS_TILES_URL ?? 'http://martin:3000',
|
|
provider: process.env.MAPS_PROVIDER ?? 'antlaq',
|
|
// خرائط انطلق (map-saas). مفتاح افتراضي + مفاتيح لكل دولة.
|
|
baseUrl: process.env.MAPS_BASE_URL ?? 'https://map-saas.intaleqapp.com',
|
|
apiKey: process.env.MAPS_API_KEY ?? '',
|
|
apiKeyByCountry: {
|
|
jo: process.env.MAPS_API_KEY_JO ?? process.env.MAPS_API_KEY ?? '',
|
|
sy: process.env.MAPS_API_KEY_SY ?? process.env.MAPS_API_KEY ?? '',
|
|
} as Record<string, string>,
|
|
placesApiKey: process.env.MAPS_PLACES_API_KEY ?? process.env.MAPS_API_KEY ?? '',
|
|
},
|
|
|
|
// Nabeh — إرسال OTP عبر واتساب.
|
|
// توجيه OTP متعدد المزوّدين حسب الدولة (docs/17 — D5).
|
|
otp: {
|
|
// نصّ رسالة SMS (Kazumi/مصر). {code} يُستبدل بالرمز.
|
|
smsTemplate: process.env.OTP_SMS_TEMPLATE ?? 'Your Tripz code is {code}',
|
|
kazumi: {
|
|
username: process.env.SMS_USERNAME ?? '',
|
|
password: process.env.SMS_PASSWORD_EGYPT ?? '',
|
|
sender: process.env.SMS_SENDER ?? '',
|
|
},
|
|
},
|
|
|
|
// الرحلات: كل كم يفحص كنس الرحلات المجدولة (docs/17 — B5).
|
|
trips: {
|
|
scheduleSweepMs: parseInt(process.env.TRIP_SCHEDULE_SWEEP_MS ?? '30000', 10),
|
|
},
|
|
|
|
nabeh: {
|
|
baseUrl: process.env.NABEH_BASE_URL ?? 'https://nabeh.intaleqapp.com',
|
|
email: process.env.NABEH_EMAIL ?? '',
|
|
password: process.env.NABEH_PASSWORD ?? '',
|
|
otpType: process.env.NABEH_OTP_TYPE ?? 'text',
|
|
},
|
|
|
|
// ملاحظة: خرائط الاتصال الدولية صارت في common/phone/phone.service.ts
|
|
// (docs/17 — D1) — مصدر واحد للحقيقة بدل نسختين قد تتباعدان.
|
|
|
|
// Gemini (رؤية) — قراءة الوثائق ومطابقة الوجه.
|
|
gemini: {
|
|
apiKey: process.env.GEMINI_API_KEY ?? '',
|
|
model: process.env.GEMINI_MODEL ?? 'gemini-2.0-flash',
|
|
},
|
|
|
|
// سرّ المنصة (super-admin) — لتعيين أول أدمن قبل وجود أدمن.
|
|
platform: {
|
|
secret: process.env.PLATFORM_SECRET ?? '',
|
|
},
|
|
|
|
// تخزين ملفات الوثائق. baseUrl فارغ = محلي؛ يُضبط لسيرفر التوطين لاحقاً (docs/06).
|
|
storage: {
|
|
dir: process.env.STORAGE_DIR ?? '/app/storage',
|
|
baseUrl: process.env.STORAGE_BASE_URL ?? '',
|
|
},
|
|
|
|
// المدفوعات: توقيع HMAC على العمليات المالية (docs/17 — I6).
|
|
// مطفأ حتى يوقّع تطبيق فلاتر؛ تفعيله قبل ذلك يقطع كل سحب.
|
|
payments: {
|
|
requireSignature: process.env.PAYMENTS_REQUIRE_SIGNATURE === 'true',
|
|
},
|
|
|
|
// إشعارات FCM (اتركه فارغاً لتعطيل الإرسال — يُسجَّل فقط).
|
|
fcm: {
|
|
serverKey: process.env.FCM_SERVER_KEY ?? '',
|
|
endpoint: process.env.FCM_ENDPOINT ?? 'https://fcm.googleapis.com/fcm/send',
|
|
},
|
|
});
|