Abdulaziiiiiz/qanati-finder
0
🎯 Qanati Finder (باحث قناتي) - Technical Documentation
مرحباً بك يا صديقي المطور/الوكيل الذكي. هذا هو المستند المرجعي الأساسي (Source of Truth) لمشروع Qanati Finder. يرجى قراءة هذا الدليل بعناية قبل البدء في تعديل أو إضافة أي كود برمجى لضمان اتساق واستقرار المشروع.
🏗️ 1. الهيكل المعماري والتقني (System Architecture)
يعتمد تطبيق Qanati Finder على نموذج هجين يربط واجهة الويب مباشرة بسيرفر الباك إند ويستخدم Supabase لإدارة قواعد البيانات والمصادقة.
المكونات الرئيسية:
- الواجهة الأمامية (Frontend): صفحات HTML/CSS متجاوبة تدعم الوضع الداكن (Dark Mode) ومصممة بأسلوب Glassmorphism الفاخر باستخدام
TailwindCSSوAlpine.jsكإطار منطق خفيف. - الواجهة الخلفية (Backend): سيرفر
FastAPI(Python) يعمل ببروتوكولHTTP/1.1ويعتمد على البث الحي عبر Server-Sent Events (SSE). - محرك الكشط (Scraper Engine): نظام مدمج بـ
Playwright Async APIيقوم بجلب البيانات حياً من خرائط جوجل. - قاعدة البيانات والمصادقة (Supabase):
Supabase Authلإدارة هويات المستخدمين ودخول Google OAuth.PostgreSQLلتخزين سجلات المستخدمين، والكاش الجغرافي، وسجل التحميلات.
🔒 2. قرارات معمارية هامة وحلول للمشاكل الشائعة
تجنباً للأخطاء البرمجية المتكررة، تم بناء الكود بناءً على القواعد التالية:
💡 أ. إلغاء مكتبة supabase-py واستخدام REST Client (حل مشكلة قطع الاتصال)
- المشكلة: بيئة Hugging Face المجانية تقطع اتصالات HTTP/2 بعد سكون قصير. مكتبة
supabase-py(التي تستخدمhttpxداخلياً) تحاول فرض اتصالات HTTP/2 مما يسبب ظهور الخطأ الشهير:RemoteProtocolError: ConnectionTerminated. - الحل: تم إقصاء مكتبة
supabase-pyكلياً من السيرفر. جميع عمليات التحقق من الجلسات (JWT) أو القراءة والكتابة في قاعدة البيانات تتم باستخدام مكتبةrequestsعبر استدعاءات REST API مباشرة باستخدام بروتوكولHTTP/1.1الثابت.
💡 ب. تشغيل Playwright متوافق مع الحاويات (Async + Headless)
- المشكلة 1: السيرفر يعمل بـ FastAPI (بيئة asyncio) بينما كان السكربر قديماً متزامناً (
sync_playwright) مما يعطل السيرفر كلياً ويرمي أخطاء تعارض asyncio loop. - المشكلة 2: السيرفرات في Hugging Face تعمل بنظام Linux headless (بدون شاشة) والوضع
headless=Falseيسبب انهيار المحرك. - الحل: تم تحويل السكربر بالكامل ليعمل بشكل غير متزامن (
async_playwright) مع إطلاق المتصفح في الخلفية (headless=True). - تطابق النسخ: تم ربط وتثبيت نسخة Playwright بـ
playwright==1.40.0في ملفrequirements.txtلتتطابق تماماً مع نظام الحاوية الأساسيmcr.microsoft.com/playwright/python:v1.40.0-jammy.
💡 ج. تخزين الجلسات في متصفح Edge (Memory Storage Fallback)
- المشكلة: ميزة منع التتبع في متصفح Edge تقوم بحظر الوصول لـ
localStorageمما يمنع Supabase Client من حفظ التوكن ويتسبب في إعادة توجيه المستخدم لصفحة الدخول مراراً. - الحل: تم توفير تخزين مزدوج (Memory Storage Fallback) في أكواد الجافا سكريبت بالفرونت إند:
const memoryStorage = {};
const supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
storage: {
getItem: (key) => { try { return localStorage.getItem(key) || memoryStorage[key]; } catch(e) { return memoryStorage[key]; } },
setItem: (key, value) => { try { localStorage.setItem(key, value); } catch(e) {} memoryStorage[key] = value; },
removeItem: (key) => { try { localStorage.removeItem(key); } catch(e) {} delete memoryStorage[key]; }
},
persistSession: true,
detectSessionInUrl: true
}
});🗄️ 3. هيكل الجداول في قاعدة البيانات (Database Schema)
أ. جدول الحسابات (public.user_profiles)
يحفظ حالة الباقة والأرصدة للمستخدم (يتم إنشاؤه تلقائياً بتريجر من auth.users):
CREATE TABLE public.user_profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT NOT NULL,
is_admin BOOLEAN DEFAULT FALSE,
tier TEXT DEFAULT 'FREE' CHECK (tier IN ('FREE', 'STARTER', 'PRO')),
export_credits_limit INTEGER DEFAULT 0,
export_credits_used INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);ب. جدول كاش الشركات (public.leads)
لتجنب إعادة الكشط وحفظ البيانات محلياً لمدة 7 أيام:
CREATE TABLE public.leads (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
google_place_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
phone TEXT,
website TEXT,
rating TEXT,
country TEXT,
city TEXT,
search_keyword TEXT,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
CREATE INDEX idx_leads_city_keyword ON public.leads(city, search_keyword);ج. جدول تتبع التصدير لمنع التكرار (public.user_exports)
CREATE TABLE public.user_exports (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
keyword TEXT NOT NULL,
city TEXT NOT NULL,
leads_exported INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);د. سجل عمليات البحث للآدمن (public.search_logs)
CREATE TABLE public.search_logs (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id UUID,
query TEXT NOT NULL,
location TEXT NOT NULL,
results_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);🔄 4. سياسات ورموز السلوك التجاري المبرمجة (Business Logic Policies)
- حماية الميزات للباقة المجانية (Server-Side Blurring):
- يتم تحديد الحد الأقصى للكشط بـ 20 نتيجة في الخلفية لأصحاب باقة
FREE. - السيرفر يقوم بمسح أرقام الهواتف والمواقع واستبدالها برسالة ترويجية
+*** (اشترك في Starter)بعد النتيجة رقم 5، مما يمنع استخراج البيانات عبر Inspect Element. - أمان الترخيص (Stream Token Concept):
- لأن بث الـ SSE لا يقبل ترويسات مخصصة (Custom Headers) مثل
Authorizationبأمان، يقوم المتصفح بطلب تذكرة مرور مؤقتة عبرPOST /api/search/authorizeمع تمرير الـ JWT. - يقوم السيرفر بالتحقق من الـ JWT ثم يولد تذكرة مرور فريدة (Stream Token) ويحفظها في الذاكرة لفترة قصيرة.
- يقوم المتصفح بفتح اتصال SSE مع السيرفر ممرراً التذكرة كمعامل استعلام (Query Param).
- منع تكرار خصم الأرصدة (Export Idempotency):
- في مسار
/api/export، إذا طلب المستخدم تصدير بيانات لمدينة وكلمة بحث سبق له تصديرها خلال آخر 7 أيام، يتم إرسال الملف له مجاناً دون خصم أي رصيد من باقته. - إذا كان البحث جديداً، يتم خصم 1 رصيد مقابل كل سطر (شركة) مصدّرة من حد حساب المستخدم.
🗺️ 5. حالة خارطة الطريق والخطوات المستقبلية
🟢 ما تم إنجازه بنجاح:
- [x] الواجهة الأمامية بهوية Qanati Tech المتجاوبة والأنيقة.
- [x] الدخول الموحد بـ (Email+Password) والدخول السريع بـ (Google OAuth).
- [x] محرك الكشط والبث الحي والـ Cache في قاعدة البيانات.
- [x] لوحة تحكم الآدمن لإدارة الباقات والأرصدة يدوياً.
- [x] حماية التصدير والتسجيل الإجباري وذاكرة تخزين Edge.
- [x] الترجمة التلقائية الذكية للبحث (Smart Query Translation) لترجمة كلمات البحث للغات المحلية الجغرافية لزيادة دقة وجودة النتائج.
- [x] أشرطة التصفية والفرز الحية للنتائج (حسب التقييم الأعلى، الهواتف، المواقع، والإيميلات).
- [x] سجل التصدير الدائم (Export History) لمراجعة وتحميل الملفات المصدرة سابقاً مجاناً ومدى الحياة.
🟡 ما هو مؤجل أو تحت التطوير:
- [ ] بوابة الدفع (Paddle / Stripe): تم تأجيلها حالياً ريثما يحصل الـ CEO على موافقة بادل الرسمية لدعم التطبيق.
- [ ] أزرار الاتصال بـ PRO: روابط الواتساب المباشرة وتوجيه GPS.
- [x] نظام البروكسي (Proxy Pool): حماية السيرفر وتدوير البروكسيات عشوائياً في Playwright مع Fallback تلقائي.
- [x] إثراء البيانات (Data Enrichment): استخراج الإيميلات وحسابات التواصل آلياً من مواقع الشركات.
نصيحة تقنية للوكلاء القادمين: عند إجراء أي تعديل برمي على الباك إند، احرص على استخدام دوالsb_selectوsb_updateوsb_insertالمبنية على مكتبةrequestsفي ملفmain.pyبدلاً من محاولة استيرادsupabase-pyلعدم كسر النظام وتجنب أخطاء HTTP/2.
