# سامانهٔ جامع آمار و اطلاعات «دیدوان» داشبورد زندهٔ بازار فلزات، فولاد، ارز، طلا/سکه، کامودیتی و کریپتو — به‌همراه آرشیو گزارش‌های منابع معتبر جهانی. این سند برای **توسعه‌دهندهٔ بعدی** نوشته شده؛ کل معماری، نحوهٔ کار، راه‌اندازی، امنیت و دام‌ها (gotchas) را پوشش می‌دهد. کامل بخوان. --- ## ۱) این سیستم چیست؟ یک اپلیکیشن سه‌بخشی: | بخش | تکنولوژی | پورت | نقش | |---|---|---|---| | **فرانت‌اند** | TanStack Start + React 19 + Vite 7 + Tailwind v4 | `5174` | لندینگ، ورود/ثبت‌نام، داشبورد | | **بک‌اند (API)** | FastAPI + SQLite + Playwright | `8000` | اسکرپرها، API دادهٔ بازار، احراز هویت | | **پنل ادمین** | FastAPI (HTML درون‌خطی) | `8001` | بازبینی/تأیید کاربران، مدیریت توکن، سلامت سرویس‌ها، گزارش‌ها | دیتای بازار با **اسکرپرهای پس‌زمینه** جمع می‌شود و در **SQLite** ذخیره می‌شود (فقط مقادیرِ تغییرکرده — dedup). فرانت‌اند هر چند ثانیه از API می‌خواند و «زنده» نشان می‌دهد. --- ## ۲) ساختار پوشه‌ها ``` asianmetal-dashboard/ ├─ src/ # فرانت‌اند │ ├─ routes/ # مسیرهای فایل‌محورِ TanStack │ │ ├─ index.tsx # لندینگ (LaserFlow، پنل زنده، گالری‌ها، …) │ │ ├─ login.tsx # ورود (کاراکترهای کارتونی + احراز واقعی) │ │ ├─ register.tsx # ثبت‌نام (→ pending) │ │ ├─ dashboard.tsx # داشبورد + همهٔ تب‌ها/کارت‌ها/چارت‌ها │ │ └─ __root.tsx # لایهٔ ریشه (RTL، فونت Vazirmatn، لود styles) │ ├─ components/ # LaserFlow, CircularGallery, CardSwap, TubelightNavbar, … │ ├─ components/ui/ # shadcn/ui │ ├─ lib/ # labels.ts، metals-data.ts، utils.ts │ ├─ styles.css # توکن‌های تم (oklch)، .tile/.glass/.panel، انیمیشن‌ها │ └─ routeTree.gen.ts # خودکار توسط TanStack (دستی ویرایش نکن) │ ├─ backend/ # API + اسکرپرها │ ├─ main.py # اپ FastAPI، CORS/هدرها/ریت‌لیمیت، همهٔ اندپوینت‌ها، lifespan │ ├─ db.py # اتصال SQLite + init_db (ساخت/مهاجرت جداول) │ ├─ auth.py # هشِ رمز (PBKDF2) + نشست‌های سمت‌سرور │ ├─ currency_stream.py # اسکرپِ ارز/طلا/سکهٔ داخلی (Playwright، هر ۶۰ث) │ ├─ crypto_market.py # کریپتو از API رایگانِ CoinGecko (هر ۶۰ث) │ ├─ world_economy.py # شاخص‌های کلانِ اقتصادهای بزرگ (هفتگی) │ ├─ steel_stocks.py # سهام شرکت‌های فولادی (companiesmarketcap، هر ۵ دقیقه) │ ├─ live_stream.py # کامودیتی لحظه‌ای (Yahoo Finance) روی WebSocket │ ├─ reports.py # اسکنِ آرشیو PDFِ گزارش‌ها (فایل‌سیستم) │ └─ sync.py # اسکریپت دستیِ سینکِ قیمت فلزات از API منبعِ بالادست │ ├─ admin/admin_app.py # پنل ادمین (مستقل) ├─ vite.config.ts # پورت ۵۱۷۴ + proxy (/api,/ws → :8000) + allowedHosts ├─ .gitignore # شاملِ .env، __pycache__، *.db └─ README.md # همین فایل ``` --- ## ۳) راه‌اندازی محلی **پیش‌نیازها:** Node 20+ (یا Bun)، Python 3.11+، و برای اسکرپرها `playwright install chromium`. ```powershell # نصب وابستگی‌های فرانت npm install # نصب وابستگی‌های پایتون (نمونه) pip install fastapi uvicorn playwright requests yfinance pandas websockets python -m playwright install chromium ``` ### اجرای هر سه سرویس (هرکدام در ترمینال جدا) ```powershell # ۱) بک‌اند (پورت 8000) $env:PYTHONUTF8="1"; python -m uvicorn main:app --host 127.0.0.1 --port 8000 --app-dir backend # ۲) فرانت‌اند (پورت 5174) npm run dev # ۳) پنل ادمین (پورت 8001) — ADMIN_PASSWORD الزامی است $env:ADMIN_USER="admin"; $env:ADMIN_PASSWORD="یک‌رمزِ‌قوی"; python -m uvicorn admin_app:app --host 127.0.0.1 --port 8001 --app-dir admin ``` سپس: فرانت روی `http://localhost:5174` ، پنل ادمین روی `http://localhost:8001`. --- ## ۴) متغیرهای محیطی | متغیر | کجا | پیش‌فرض | توضیح | |---|---|---|---| | `DB_PATH` | backend, admin | مسیر محلی | محل فایل SQLite (هر دو سرویس باید یکی باشند) | | `ASIANMETAL_TOKEN` | sync.py | — | توکنِ API بالادست (یا از پنل ادمین در DB) — **هیچ‌وقت در کد هاردکد نکن** | | `ADMIN_USER` / `ADMIN_PASSWORD` | admin | `admin` / **الزامی** | احراز Basic پنل ادمین؛ بدون `ADMIN_PASSWORD` بالا نمی‌آید (fail-closed) | | `ALLOWED_ORIGINS` | backend | `localhost:5174,127.0.0.1:5174` | originهای مجاز CORS (با کاما) | | `COOKIE_SECURE` | backend | `0` | در پروداکشن (HTTPS) روی `1` بگذار تا کوکیِ نشست Secure شود | | `REPORTS_DIR` | reports.py | پوشهٔ آرشیو | محل PDFهای گزارش (ساختار `<منبع> - <دسته>/.pdf`) | --- ## ۵) دیتابیس (SQLite) `init_db()` در `db.py` جداول را می‌سازد و **مهاجرتِ امن** انجام می‌دهد (ستون‌ها را اگر نبودند `ALTER` می‌کند): - **`prices`** — تاریخچهٔ روزانهٔ قیمت فلزات: `grp, category, title, date, low, mid, high` (یکتا روی `title,date`). منبعِ تب «فلزات و فولاد». - **`users`** — کاربران: `id, name, email, phone, organization, password_hash, status('pending'|'approved'|'rejected'), created_at`. - **`sessions`** — نشست‌های سمت‌سرور: `token, user_id, created_at, expires_at`. - **`app_config`** — جفت‌های `key/value` (مثلاً `asianmetal_token`، هارت‌بیت اسکرپرها). - جداول اسکرپرها: `currency_prices, gold_prices, coin_prices`, `te_prices`, `world_economy`, `steel_stocks` — هرکدام آخرین/تاریخچهٔ مقادیر را نگه می‌دارند (dedup روی تغییر). > نکته: قیمت‌های لحظه‌ای (ارز/کریپتو/کامودیتی) عمدتاً در **حافظه** کش می‌شوند (`_latest`) و فقط تغییرها در DB ذخیره می‌شوند تا حجم کم بماند. --- ## ۶) اسکرپرها (در `lifespan` بک‌اند خودکار استارت می‌شوند) | ماژول | منبع | بازه | جدول/خروجی | |---|---|---|---| | `currency_stream` | نرخ داخلیِ ارز/طلا/سکه (URL در همان فایل) | ۶۰ ثانیه | `currency_prices`/`gold_prices`/`coin_prices` | | `crypto_market` | **CoinGecko** (API رایگان، بدون کلید) | ۶۰ ثانیه | حافظه (`get_latest`) | | `world_economy` | شاخص‌های کلانِ اقتصادهای بزرگ | هفتگی | `world_economy` | | `steel_stocks` | companiesmarketcap.com | ۵ دقیقه | `steel_stocks` | | `live_stream` | **Yahoo Finance** | لحظه‌ای | WebSocket `/ws/live` | | `reports` | فایل‌سیستمِ `REPORTS_DIR` | کشِ ۱۰ دقیقه | اسکنِ PDF | **دامِ مهم:** سایت‌های منبع قیمت‌ها را با JS لِیزی‌لود می‌کنند. اسکرپرهای Playwright بعد از `goto` یک `wait_for_timeout(5000–9000)` دارند؛ **حذفش نکن** وگرنه مقادیرِ کهنه/خالی خوانده می‌شود. هر اسکرپر هم try/except دارد تا یک خطای گذرا کلِ تسک پس‌زمینه را نکُشد. `sync.py` یک اسکریپتِ **دستی** است (در lifespan نیست) که قیمت فلزات را از API بالادست می‌گیرد؛ توکن را از `app_config` یا env `ASIANMETAL_TOKEN` می‌خواند. --- ## ۷) اندپوینت‌های API (بک‌اند `:8000`) **عمومی (دادهٔ بازار):** `GET /api/tree`, `/api/history?title=&days=`, `/api/search?q=`, `/api/latest`, `/api/live`, `/api/currency`, `/api/gold`, `/api/coin`, `/api/world-economy`, `/api/steel-stocks`, `/api/crypto`, `/api/reports`, `/api/reports/files?folder=`, `/api/reports/file?folder=&name=` (استریمِ inline PDF با گاردِ traversal)، WebSocket `/ws/live`. **احراز هویت:** `POST /api/register`، `POST /api/login`، `GET /api/me`، `POST /api/logout`. **پنل ادمین `:8001`** (پشت HTTP Basic): `/api/users`، `POST /api/users/{id}/approve|reject`، `GET|POST /api/token`، `/api/health`، `/api/test-scrape`، `/api/reports`، `/api/export?dataset=`. --- ## ۸) احراز هویت و گردشِ تأیید (مهم) > اصل: **هیچ اعتمادی به کلاینت نیست.** ورود کاملاً سمت‌سرور تأیید می‌شود؛ نشست در **کوکیِ httpOnly** است، نه `localStorage`. ``` ثبت‌نام (نام، نام‌خانوادگی، سازمان، ایمیل، موبایل، رمز) │ POST /api/register → رمز با PBKDF2 هش می‌شود، رکورد با status='pending' ▼ کاربر در وضعیت «در انتظار تأیید» ──► ادمین در پنل (تبِ کاربران) تأیید/رد می‌کند │ (POST /api/users/{id}/approve | reject) ▼ ورود POST /api/login → اگر approved بود: کوکیِ نشست (httpOnly, SameSite=Lax) ست می‌شود │ pending → 403 «در انتظار تأیید» ، rejected → 403 ، رمز غلط → 401 ▼ داشبورد → گاردِ سمت‌سرور: fetch('/api/me')؛ اگر 401 شد → ری‌دایرکت به /login خروج POST /api/logout → نشست از DB حذف + کوکی پاک می‌شود ``` نکات پیاده‌سازی: - هشِ رمز: `auth.hash_password` با `hashlib.pbkdf2_hmac('sha256', ..., 200000)` — بدونِ وابستگیِ خارجی. مقایسه با `hmac.compare_digest`. - توکنِ نشست: `secrets.token_urlsafe(32)`، انقضای ۷ روزه، در جدول `sessions`. - ریت‌لیمیتِ per-IP (در حافظه) روی `register`/`login` (۵ در دقیقه). - اعتبارسنجیِ Pydantic: فرمت/طولِ ایمیل و موبایل، حداقل ۸ کاراکترِ رمز. - رد کردنِ کاربر، نشست‌های فعالش را هم پاک می‌کند (دسترسی فوراً قطع). --- ## ۹) پنل ادمین (`:8001`) - ورود با HTTP Basic؛ `ADMIN_PASSWORD` **الزامی** است (بدونش بالا نمی‌آید). - تب‌ها: **کاربران** (لیست + تأیید/رد + سازمان/وضعیت)، **توکن منبع داده** (ذخیره/تستِ توکن بالادست)، **سلامت سرویس‌ها** (هارت‌بیتِ هر اسکرپر + تست اسکرپ)، **گزارشات** (دیتاست‌ها + خروجی CSV). - خروجیِ کاربران در UI با `esc()` فرار داده می‌شود (جلوگیری از XSSِ ذخیره‌شده). --- ## ۱۰) فرانت‌اند - مسیرها: `/` (لندینگ)، `/login`، `/register`، `/dashboard`. - داشبورد، تب‌ها (`VIEW_NAV`): **بازار داخلی**، **کامودیتی**، **فلزات و فولاد** (کاتالوگِ کارت + چارت)، **اقتصاد جهانی** (جدول حرارتی)، **سهام فولادی**، **کریپتو**، **گزارش‌ها**. - زبان بصری: تم **لایتِ کرم + لهجهٔ کهربایی (فولاد مذاب)**؛ توکن‌ها در `styles.css` (oklch). قیمت‌کارت‌ها سبکِ **Spark-canvas** (نمودار پس‌زمینه + رقم‌های غلتانِ `Odometer` + پیلِ درصد). - API نسبی است (`const API = ""`) تا از طریق **پراکسیِ Vite** هم‌مبدأ کار کند → برای share فقط یک تونل لازم است. - RTL سراسری، فونت **Vazirmatn**. اعداد همه با همین فونت (نه مونو). --- ## ۱۱) اشتراک‌گذاری (ngrok) چون `vite.config.ts` مسیرهای `/api` و `/ws` را به `:8000` پراکسی می‌کند و فرانت از URLهای نسبی استفاده می‌کند، **فقط یک تونل روی ۵۱۷۴ کافی است**: ```powershell ngrok http 5174 # یا مستقیم باینری: & "$env:APPDATA\npm\node_modules\ngrok\bin\ngrok.exe" http 5174 ``` نکته: شیمِ `ngrok.ps1`ی npm گاهی پورت را اشتباه پاس می‌دهد؛ اگر تونل به پورت غلط رفت، **مستقیم `ngrok.exe`** را صدا بزن. URL را از `http://127.0.0.1:4040/api/tunnels` هم می‌توانی بخوانی. (پلنِ رایگان: هر بار URL عوض می‌شود و یک صفحهٔ هشدارِ اولیه دارد.) --- ## ۱۲) امنیت — وضعیت و کارهای واجب سخت‌سازی‌های انجام‌شده: CORS محدود، هدرهای امنیتی (`nosniff`, `X-Frame-Options`, `Referrer-Policy`)، اعتبارسنجی + ریت‌لیمیتِ ورودی‌ها، محدودیتِ کوئریِ `history`/`search`، گاردِ path-traversal روی استریمِ PDF، کوئری‌های پارامتری، احراز هویتِ سمت‌سرور با کوکیِ httpOnly، و حذفِ توکنِ هاردکد از کد. **واجب قبل از پروداکشن:** 1. توکنِ بالادست (SteelStatista) که قبلاً در git history لو رفته را **باطل/تمدید** کن؛ مقدارِ جدید فقط در پنل ادمین/`env`. 2. `ADMIN_PASSWORD` قوی بگذار (پیش‌فرضِ ضعیف حذف شده). 3. در HTTPS واقعی: `COOKIE_SECURE=1` و `ALLOWED_ORIGINS` را به دامنهٔ واقعی محدود کن. 4. پنل ادمین (`:8001`) را **عمومی نکن** (در تونل نگذار؛ فقط لوکال/VPN/SSH). --- ## ۱۳) دام‌ها و بدهیِ فنی (برای دفعهٔ بعد) - `DB_PATH`/مسیرها در `db.py` و `admin_app.py` پیش‌فرضِ ویندوزیِ مطلق دارند (شاملِ نام کاربری) — بهتره به مسیر نسبی/`env` منتقل شود. - وابستگی‌های `package.json` با `^` پین نشده‌اند؛ برای بیلدِ تکرارپذیر `npm ci` + پین. - فایل‌های `__pycache__/*.pyc` قبلاً کامیت شده‌اند؛ untrack کن: `git rm -r --cached backend/__pycache__`. - `routeTree.gen.ts` خودکار است — دستی ویرایش نکن (با اضافه‌شدن فایل route، خودِ Vite بازتولید می‌کند). - اسکرپرها به ساختار HTMLِ منابع وابسته‌اند؛ اگر منبع قالبش را عوض کند، سلکتورها/`wait_for_timeout` باید تنظیم شوند.