CoolFace
Apppublic

Abdulaziiiiiz/qanati-finder

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
App README

🎯 Qanati Finder (باحث قناتي) - Technical Documentation

مرحباً بك يا صديقي المطور/الوكيل الذكي. هذا هو المستند المرجعي الأساسي (Source of Truth) لمشروع Qanati Finder. يرجى قراءة هذا الدليل بعناية قبل البدء في تعديل أو إضافة أي كود برمجى لضمان اتساق واستقرار المشروع.


🏗️ 1. الهيكل المعماري والتقني (System Architecture)

يعتمد تطبيق Qanati Finder على نموذج هجين يربط واجهة الويب مباشرة بسيرفر الباك إند ويستخدم Supabase لإدارة قواعد البيانات والمصادقة.

المكونات الرئيسية:

  1. 1.الواجهة الأمامية (Frontend): صفحات HTML/CSS متجاوبة تدعم الوضع الداكن (Dark Mode) ومصممة بأسلوب Glassmorphism الفاخر باستخدام TailwindCSS و Alpine.js كإطار منطق خفيف.
  2. 2.الواجهة الخلفية (Backend): سيرفر FastAPI (Python) يعمل ببروتوكول HTTP/1.1 ويعتمد على البث الحي عبر Server-Sent Events (SSE).
  3. 3.محرك الكشط (Scraper Engine): نظام مدمج بـ Playwright Async API يقوم بجلب البيانات حياً من خرائط جوجل.
  4. 4.قاعدة البيانات والمصادقة (Supabase):
  5. 5.Supabase Auth لإدارة هويات المستخدمين ودخول Google OAuth.
  6. 6.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) في أكواد الجافا سكريبت بالفرونت إند:
javascript
    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):

sql
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 أيام:

sql
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)

sql
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)

sql
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)

  1. 1.حماية الميزات للباقة المجانية (Server-Side Blurring):
  2. 2.يتم تحديد الحد الأقصى للكشط بـ 20 نتيجة في الخلفية لأصحاب باقة FREE.
  3. 3.السيرفر يقوم بمسح أرقام الهواتف والمواقع واستبدالها برسالة ترويجية +*** (اشترك في Starter) بعد النتيجة رقم 5، مما يمنع استخراج البيانات عبر Inspect Element.
  4. 4.أمان الترخيص (Stream Token Concept):
  5. 5.لأن بث الـ SSE لا يقبل ترويسات مخصصة (Custom Headers) مثل Authorization بأمان، يقوم المتصفح بطلب تذكرة مرور مؤقتة عبر POST /api/search/authorize مع تمرير الـ JWT.
  6. 6.يقوم السيرفر بالتحقق من الـ JWT ثم يولد تذكرة مرور فريدة (Stream Token) ويحفظها في الذاكرة لفترة قصيرة.
  7. 7.يقوم المتصفح بفتح اتصال SSE مع السيرفر ممرراً التذكرة كمعامل استعلام (Query Param).
  8. 8.منع تكرار خصم الأرصدة (Export Idempotency):
  9. 9.في مسار /api/export، إذا طلب المستخدم تصدير بيانات لمدينة وكلمة بحث سبق له تصديرها خلال آخر 7 أيام، يتم إرسال الملف له مجاناً دون خصم أي رصيد من باقته.
  10. 10.إذا كان البحث جديداً، يتم خصم 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.