D1 — تطبيع أرقام الهاتف (JO/EG/SY): - common/phone/phone.service.ts: مفتاح قانوني واحد لكل رقم حقيقي — يقبل صفراً محلياً/دولياً/+/00/بلا صفر ويرجع صيغة دولية موحّدة بلا + - يعالج لبس مصر تحديداً الذي ذكره المالك: "01012345678" يُطبَّع بمفتاح 20 الصحيح لا "2" — الخوارزمية عامة فلا تحتاج معرفة اللبس، فقط تحذف الصفر البادئ وتضيف مفتاح الدولة الحقيقي - send-otp و verify-otp و platform/users/role تمرّ كلها بالتطبيع الآن؛ كانت تخزّن/تبحث بالرقم الخام كما كُتب — رقم واحد بصيغتين = حسابان - هجرة NormalizePhones تُصحّح حسابات الاختبار الموجودة على السيرفر، بحذر: تتخطّى أي تصادم بدل كسر القيد الفريد (tenant_id, phone) - أُزيل config.callingCodes المكرّر — مصدر واحد للحقيقة D2 — ربط الجلسة بالجهاز (نمط سيرو): **مبنيّ ومطفأ** (AUTH_REQUIRE_DEVICE_BINDING=false) حتى يرسل فلاتر x-device-id. منفَّذ داخل JwtStrategy.validate نفسها (passReqToCallback) لا كحارس يُضاف يدوياً لكل متحكّم — فلا نقطة محميّة يمكن نسيانها. التوكن يحمل hash(deviceId) لا القيمة الخام. verifyOtp/refresh يمرّران x-device-id من المتحكّم عند إصدار التوكن. D3 — HMAC: منفَّذ فعلاً ضمن I6 (SigningService/SignatureGuard)، لا تكرار. خطأ ضبطته قبل الدفع: كتبت مفتاح `auth:` ثانياً في configuration.ts — كائنات JS تسمح بمفاتيح مكرّرة والأخير يطغى، فكان سيمحو otpDevMode/otpTtl/ otpLength بالكامل. دُمج في الكتلة الأصلية بدل مفتاح جديد. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.2 KiB
TypeScript
51 lines
2.2 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
/**
|
|
* تطبيع أرقام الهاتف المخزَّنة مسبقاً (docs/17 — D1).
|
|
*
|
|
* قبل هذه الهجرة كانت `sendOtp`/`verifyOtp` تخزّنان الرقم **كما كُتب حرفياً**
|
|
* — فحسابات اختبار الحمل (`0797…`) وأي دخول سابق مخزَّنة بصيغة محلية لا
|
|
* دولية. تطبّق هذه الهجرة نفس منطق `PhoneService` بـSQL خام لكل مستأجر حسب
|
|
* `country_pack`، وتتخطّى أي صفٍّ يتعارض ناتجه مع صفٍّ آخر (لا تكسر القيد
|
|
* الفريد `(tenant_id, phone)`؛ التعارض النادر يُترك يدوياً).
|
|
*/
|
|
export class NormalizePhones1721880000000 implements MigrationInterface {
|
|
public async up(q: QueryRunner): Promise<void> {
|
|
// jo/sy: صفر محلي + 9 أرقام → مفتاح الدولة + 9 أرقام.
|
|
await q.query(`
|
|
UPDATE tripz_users u
|
|
SET phone = t.country_pack_cc || substring(u.phone from 2)
|
|
FROM (
|
|
SELECT id, CASE country_pack WHEN 'jo' THEN '962' WHEN 'sy' THEN '963' END AS country_pack_cc
|
|
FROM tripz_tenants WHERE country_pack IN ('jo', 'sy')
|
|
) t
|
|
WHERE u.tenant_id = t.id
|
|
AND u.phone ~ '^0[0-9]{9}$'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM tripz_users u2
|
|
WHERE u2.tenant_id = u.tenant_id
|
|
AND u2.phone = t.country_pack_cc || substring(u.phone from 2)
|
|
)
|
|
`);
|
|
|
|
// eg: صفر محلي + 10 أرقام → "20" + 10 أرقام.
|
|
await q.query(`
|
|
UPDATE tripz_users u
|
|
SET phone = '20' || substring(u.phone from 2)
|
|
FROM tripz_tenants t
|
|
WHERE u.tenant_id = t.id
|
|
AND t.country_pack = 'eg'
|
|
AND u.phone ~ '^0[0-9]{10}$'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM tripz_users u2
|
|
WHERE u2.tenant_id = u.tenant_id AND u2.phone = '20' || substring(u.phone from 2)
|
|
)
|
|
`);
|
|
}
|
|
|
|
public async down(): Promise<void> {
|
|
// لا رجوع — لا سبيل لمعرفة الصيغة الأصلية بعد التطبيع (وقد تكون كانت
|
|
// بصيغ مختلفة أصلاً). التطبيع أُحادي الاتجاه بطبيعته.
|
|
}
|
|
}
|