#is', $trHtml, $tdMatches);
+ $tds = array_map(fn($t) => trim(strip_tags($t)), $tdMatches[1] ?? []);
+ if (count($tds) >= 10 && (strtolower($tds[2] ?? '') === 'buy' || strtolower($tds[2] ?? '') === 'sell')) {
+ $ticket = $tds[0];
+ $openTime = date('Y-m-d H:i:s', strtotime($tds[1]));
+ $type = strtolower($tds[2]);
+ $size = (float)$tds[3];
+ $symbol = strtoupper($tds[4]);
+ $openPrice = (float)$tds[5];
+ $sl = (float)($tds[6] ?? 0);
+ $tp = (float)($tds[7] ?? 0);
+ $closeTime = isset($tds[8]) ? date('Y-m-d H:i:s', strtotime($tds[8])) : $openTime;
+ $closePrice = (float)($tds[9] ?? $openPrice);
+ $comm = (float)($tds[10] ?? 0);
+ $swap = (float)($tds[11] ?? 0);
+ $profit = (float)($tds[12] ?? 0);
+
+ $pips = calculatePips($symbol, $type, $openPrice, $closePrice);
+
+ $stmtIns = $pdo->prepare("
+ INSERT INTO trades (
+ account_id, ticket, symbol, trade_type, lot_size,
+ open_price, close_price, stop_loss, take_profit,
+ open_time, close_time, pips, profit, commission, swap, status, strategy, emotion
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'closed', 'MT4/MT5 Statement', 'Disciplined')
+ ");
+ $stmtIns->execute([$accountId, $ticket, $symbol, $type, $size, $openPrice, $closePrice, $sl, $tp, $openTime, $closeTime, $pips, $profit, $comm, $swap]);
+ $importedCount++;
+ }
+ }
+ }
+
+ sendJson(['success' => true, 'imported_count' => $importedCount, 'message' => "تعداد $importedCount معامله با موفقیت ایمپورت شد"]);
+ }
+
+ // -------------------------------------------------------------
+ // 5. EXPORT TRADES (CSV)
+ // -------------------------------------------------------------
+ if (preg_match('#^/api/trades/export/?$#', $uri) && $method === 'GET') {
+ $accountId = (int)($_GET['account_id'] ?? 1);
+ $stmt = $pdo->prepare("SELECT * FROM trades WHERE account_id = ? ORDER BY open_time ASC");
+ $stmt->execute([$accountId]);
+ $trades = $stmt->fetchAll();
+
+ header('Content-Type: text/csv; charset=utf-8');
+ header('Content-Disposition: attachment; filename="forex_trades_' . date('Ymd_His') . '.csv"');
+
+ $out = fopen('php://output', 'w');
+ // UTF-8 BOM for Excel
+ fputs($out, "\xEF\xBB\xBF");
+ fputcsv($out, ['ID', 'Ticket', 'Symbol', 'Type', 'Volume', 'Open Price', 'Close Price', 'S/L', 'T/P', 'Open Time', 'Close Time', 'Pips', 'Profit ($)', 'Commission', 'Swap', 'Status', 'Strategy', 'Timeframe', 'Session', 'Emotion', 'Notes']);
+
+ foreach ($trades as $t) {
+ fputcsv($out, [
+ $t['id'],
+ $t['ticket'],
+ $t['symbol'],
+ $t['trade_type'],
+ $t['lot_size'],
+ $t['open_price'],
+ $t['close_price'],
+ $t['stop_loss'],
+ $t['take_profit'],
+ $t['open_time'],
+ $t['close_time'],
+ $t['pips'],
+ $t['profit'],
+ $t['commission'],
+ $t['swap'],
+ $t['status'],
+ $t['strategy'],
+ $t['timeframe'],
+ $t['session'],
+ $t['emotion'],
+ $t['entry_notes']
+ ]);
+ }
+ fclose($out);
+ exit;
+ }
+
+ // -------------------------------------------------------------
+ // 6. SCREENSHOT IMAGE UPLOAD
+ // -------------------------------------------------------------
+ if (preg_match('#^/api/upload/?$#', $uri) && $method === 'POST') {
+ if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
+ sendJson(['success' => false, 'error' => 'تصویری ارسال نشد یا خطایی رخ داد'], 400);
+ }
+
+ $file = $_FILES['image'];
+ $allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
+ if (!in_array($file['type'], $allowed)) {
+ sendJson(['success' => false, 'error' => 'فرمت تصویر نامعتبر است (تنها JPG, PNG, WEBP مجاز است)'], 400);
+ }
+
+ $uploadDir = __DIR__ . '/uploads';
+ if (!is_dir($uploadDir)) mkdir($uploadDir, 0777, true);
+
+ $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
+ $newFilename = 'chart_' . uniqid() . '.' . $ext;
+ $dest = $uploadDir . '/' . $newFilename;
+
+ if (move_uploaded_file($file['tmp_name'], $dest)) {
+ sendJson(['success' => true, 'url' => '/uploads/' . $newFilename]);
+ } else {
+ sendJson(['success' => false, 'error' => 'ذخیره تصویر با خطا مواجه شد'], 500);
+ }
+ }
+
+ // -------------------------------------------------------------
+ // 7. STRATEGIES & DAILY LOGS
+ // -------------------------------------------------------------
+ if (preg_match('#^/api/strategies/?$#', $uri)) {
+ if ($method === 'GET') {
+ $stmt = $pdo->query("SELECT * FROM strategies ORDER BY name ASC");
+ sendJson(['success' => true, 'strategies' => $stmt->fetchAll()]);
+ } elseif ($method === 'POST') {
+ $input = getJsonInput();
+ $name = trim($input['name'] ?? '');
+ if (empty($name)) sendJson(['success' => false, 'error' => 'نام استراتژی الزامی است'], 400);
+ $stmt = $pdo->prepare("INSERT OR IGNORE INTO strategies (name, description, color) VALUES (?, ?, ?)");
+ $stmt->execute([$name, $input['description'] ?? '', $input['color'] ?? '#3b82f6']);
+ sendJson(['success' => true, 'message' => 'استراتژی ثبت شد']);
+ }
+ }
+
+ // -------------------------------------------------------------
+ // 8. RESET & SEED SAMPLE DATA
+ // -------------------------------------------------------------
+ if (preg_match('#^/api/reset_sample/?$#', $uri) && $method === 'POST') {
+ $accountId = (int)($_GET['account_id'] ?? 1);
+ $pdo->exec("DELETE FROM trades WHERE account_id = $accountId");
+ Database::generateSampleTrades($accountId, 10000.0);
+ sendJson(['success' => true, 'message' => 'دادههای نمونه مایافایکسبوک با موفقیت بازیابی شدند']);
+ }
+
+ sendJson(['success' => false, 'error' => 'مسیر مورد نظر یافت نشد (API Not Found)'], 404);
+
+} catch (Throwable $e) {
+ sendJson(['success' => false, 'error' => 'Server Error: ' . $e->getMessage()], 500);
+}
diff --git a/database.py b/database.py
deleted file mode 100644
index 394e6df..0000000
--- a/database.py
+++ /dev/null
@@ -1,280 +0,0 @@
-import sqlite3
-import os
-import random
-from datetime import datetime, timedelta
-
-DB_PATH = os.path.join(os.path.dirname(__file__), "urban_issues.db")
-
-def get_db_connection():
- conn = sqlite3.connect(DB_PATH)
- conn.row_factory = sqlite3.Row
- return conn
-
-def init_db():
- conn = get_db_connection()
- cursor = conn.cursor()
-
- # Issues Table
- cursor.execute("""
- CREATE TABLE IF NOT EXISTS issues (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- tracking_code TEXT UNIQUE NOT NULL,
- title TEXT NOT NULL,
- category TEXT NOT NULL,
- description TEXT NOT NULL,
- address TEXT NOT NULL,
- district INTEGER NOT NULL,
- priority TEXT NOT NULL, -- 'low', 'medium', 'high', 'emergency'
- status TEXT NOT NULL, -- 'pending', 'reviewing', 'in_progress', 'resolved', 'rejected'
- lat REAL NOT NULL,
- lng REAL NOT NULL,
- image_url TEXT,
- resolved_image_url TEXT,
- reporter_name TEXT,
- reporter_phone TEXT,
- official_response TEXT,
- upvotes INTEGER DEFAULT 0,
- created_at TEXT NOT NULL,
- resolved_at TEXT
- )
- """)
-
- # Comments Table
- cursor.execute("""
- CREATE TABLE IF NOT EXISTS comments (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- issue_id INTEGER NOT NULL,
- author_name TEXT NOT NULL,
- content TEXT NOT NULL,
- created_at TEXT NOT NULL,
- FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
- )
- """)
-
- # Timeline Table
- cursor.execute("""
- CREATE TABLE IF NOT EXISTS timeline (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- issue_id INTEGER NOT NULL,
- status TEXT NOT NULL,
- title TEXT NOT NULL,
- description TEXT,
- created_at TEXT NOT NULL,
- FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
- )
- """)
-
- # Check if empty, then seed initial realistic data
- cursor.execute("SELECT COUNT(*) FROM issues")
- count = cursor.fetchone()[0]
-
- if count == 0:
- seed_data(cursor)
-
- conn.commit()
- conn.close()
-
-def seed_data(cursor):
- now = datetime.now()
-
- sample_issues = [
- {
- "tracking_code": "SHR-78214",
- "title": "فرونشست و چاله عمیق در لاین کندرو",
- "category": "آسفالت و معابر",
- "description": "وجود چاله عمیق با قطر حدود ۱ متر پس از بارندگی اخیر که باعث خسارت به لاستیک خودروها و ترمزهای ناگهانی خطرناک میشود.",
- "address": "بزرگراه شهید همت، نرسیده به خروجی گاندی، لاین کندرو",
- "district": 3,
- "priority": "emergency",
- "status": "in_progress",
- "lat": 35.7538,
- "lng": 51.4172,
- "image_url": "https://images.unsplash.com/photo-1515162816999-a0c47dc192f7?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": None,
- "reporter_name": "علی رضایی",
- "reporter_phone": "09121112233",
- "official_response": "اکیپ آسفالتریزی ناحیه ۲ شهرداری منطقه ۳ اعزام گردیده و عملیات زیرسازی و لکهگیری در حال انجام است.",
- "upvotes": 42,
- "days_ago": 2,
- "resolved_at": None,
- "timeline": [
- ("pending", "ثبت گزارش توسط شهروند", "گزارش در سامانه ۱۳۷ با موفقیت ثبت شد."),
- ("reviewing", "تایید کارشناس و ارجاع به معاونت فنی و عمران", "موضوع به ناحیه ۲ ارجاع داده شد."),
- ("in_progress", "اعزام اکیپ عملیاتی لکهگیری", "تیم فنی با تجهیزات در محل مستقر شد.")
- ],
- "comments": [
- ("سارا محمدی", "دیروز لاستیک ماشین من هم اینجا آسیب دید، ممنون که پیگیری میکنید."),
- ("حسین کاظمی", "امیدوارم امشب تا قبل از ترافیک صبحگاهی تموم بشه.")
- ]
- },
- {
- "tracking_code": "SHR-65109",
- "title": "خاموشی کامل پایه چراغهای روشنایی بوستان",
- "category": "روشنایی و برق",
- "description": "کل مسیر پیادهروی شرقی بوستان ملت در تاریکی مطلق است که باعث کاهش امنیت خانوادهها و ورزشکاران شبانه شده است.",
- "address": "خیابان ولیعصر، بوستان ملت، ضلع شرقی جنب دریاچه",
- "district": 3,
- "priority": "high",
- "status": "resolved",
- "lat": 35.7801,
- "lng": 51.4116,
- "image_url": "https://images.unsplash.com/photo-1509114397022-ed747cca3f65?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": "https://images.unsplash.com/photo-1517457373958-b7bdd4587205?auto=format&fit=crop&w=800&q=80",
- "reporter_name": "مریم احمدی",
- "reporter_phone": "09123334455",
- "official_response": "کابلکشی زیرزمینی و تعویض پروژکتورهای معیوب توسط اداره زیباسازی و تاسیسات منطقه انجام و روشنایی کامل برقرار گردید.",
- "upvotes": 68,
- "days_ago": 5,
- "resolved_at": (now - timedelta(days=1)).strftime("%Y-%m-%d %H:%M"),
- "timeline": [
- ("pending", "ثبت گزارش توسط شهروند", "گزارش دریافت شد."),
- ("reviewing", "بررسی میدانی اداره تاسیسات", "نقص کابل اصلی تایید شد."),
- ("in_progress", "تعمیرات و تعویض کابل", "تیم تاسیسات در حال اجرای خط جدید."),
- ("resolved", "تکمیل و رفع نقص روشنایی", "چراغها روشن و مدار به طور کامل تست شد.")
- ],
- "comments": [
- ("امیر رستمی", "خیلی سریع درستش کردن، واقعا تشکر از عوامل شهرداری."),
- ("نگار توکلی", "دیشب رفتم پارک، همه چراغها درست شده بود.")
- ]
- },
- {
- "tracking_code": "SHR-91203",
- "title": "انباشت پسماند و سرریز مخزن زباله شهری",
- "category": "نظافت و پسماند",
- "description": "مخزن مکانیزه زباله شکسته شده و زبالهها در پیادهرو پخش شده که باعث بوی نامطبوع و تجمع حشرات شده است.",
- "address": "خیابان آزادی، تقاطع خیابان استاد معین، پلاک ۴۲",
- "district": 9,
- "priority": "medium",
- "status": "pending",
- "lat": 35.6997,
- "lng": 51.3486,
- "image_url": "https://images.unsplash.com/photo-1532996122724-e3c354a0b15b?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": None,
- "reporter_name": "سعید کریمی",
- "reporter_phone": "09355556677",
- "official_response": "گزارش در نوبت بررسی ناظر پسماند منطقه قرار گرفته است.",
- "upvotes": 19,
- "days_ago": 1,
- "resolved_at": None,
- "timeline": [
- ("pending", "ثبت گزارش در سامانه", "منتظر بازرسی و تخصیص سطل جدید مکانیزه.")
- ],
- "comments": [
- ("مهدی بهرامی", "لطفا سطلهای پدالدار بگذارید که درش بسته بمونه.")
- ]
- },
- {
- "tracking_code": "SHR-44390",
- "title": "آسیب دیدگی و شکستن شاخههای درخت کهنسال بر اثر طوفان",
- "category": "فضای سبز و بوستانها",
- "description": "شاخههای سنگین درخت چنار شکسته و روی سیمهای برق و پیادهرو معلق مانده است، احتمال سقوط روی عابرین وجود دارد.",
- "address": "خیابان شریعتی، بالاتر از پل رومی، نبش کوچه یاس",
- "district": 1,
- "priority": "emergency",
- "status": "in_progress",
- "lat": 35.7985,
- "lng": 51.4332,
- "image_url": "https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": None,
- "reporter_name": "پویا سرمدی",
- "reporter_phone": "09124445566",
- "official_response": "اکیپ سازمان بوستانها و آتشنشانی با جرثقیل در حال هرس ایمن و رفع خطر هستند.",
- "upvotes": 54,
- "days_ago": 1,
- "resolved_at": None,
- "timeline": [
- ("pending", "ثبت فوری گزارش خطر", "هشدار سقوط شاخه دریافت شد."),
- ("reviewing", "هماهنگی با سازمان آتشنشانی و فضای سبز", "اعلام وضعیت اضطراری."),
- ("in_progress", "عملیات رفع خطر و هرس", "حصارکشی پیادهرو و استقرار بالابر.")
- ],
- "comments": [
- ("رویا صبوری", "خدا رو شکر سریع اومدن نوار خطر کشیدن.")
- ]
- },
- {
- "tracking_code": "SHR-33219",
- "title": "سد معبر طولانی مصالح ساختمانی و تخریب پیادهرو",
- "category": "ساختمانسازی و سد معبر",
- "description": "یک پروژه ساختمانی بیش از سه هفته است که تمام پیادهرو و بخشی از خیابان را با نخاله و داربست بدون راه عبور ایمن مسدود کرده است.",
- "address": "سعادتآباد، میدان کاج، خیابان مروارید، پلاک ۱۸",
- "district": 2,
- "priority": "high",
- "status": "reviewing",
- "lat": 35.7794,
- "lng": 51.3758,
- "image_url": "https://images.unsplash.com/photo-1541888946425-d0fbb18086f6?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": None,
- "reporter_name": "فرشید نادری",
- "reporter_phone": "09127778899",
- "official_response": "اخطاریه ماده ۱۰۰ و رفع سد معبر برای مالک صادر شد. مهلت رفع ۴۸ ساعت میباشد.",
- "upvotes": 37,
- "days_ago": 3,
- "resolved_at": None,
- "timeline": [
- ("pending", "ثبت گزارش تخلف ساختمانی", "ثبت شد."),
- ("reviewing", "بازدید مامور اجرای احکام شهرداری", "صدور اخطاریه رسمی رفع انسداد معبر.")
- ],
- "comments": [
- ("کامران شمس", "مادر من با ویلچر مجبور شد بره توی خیابان که خیلی خطرناک بود!")
- ]
- },
- {
- "tracking_code": "SHR-55821",
- "title": "خرابی و چشمکزن ماندن چراغ راهنمایی تقاطع پرتردد",
- "category": "ترافیک و حملونقل",
- "description": "چراغ راهنمایی تقاطع به مدت دو روز چشمکزن زرد مانده که باعث گره کور ترافیکی و تصادفات مکرر شده است.",
- "address": "میدان فاطمی (جهاد)، تقاطع خیابان جویبار و شهید گمنام",
- "district": 6,
- "priority": "high",
- "status": "resolved",
- "lat": 35.7208,
- "lng": 51.4082,
- "image_url": "https://images.unsplash.com/photo-1517649763962-0c623266ddc0?auto=format&fit=crop&w=800&q=80",
- "resolved_image_url": "https://images.unsplash.com/photo-1508873696983-2df5703bc275?auto=format&fit=crop&w=800&q=80",
- "reporter_name": "ندا زمانی",
- "reporter_phone": "09191113355",
- "official_response": "برد الکترونیکی کنترلر چراغ توسط شرکت کنترل ترافیک تهران تعویض و زمانبندی هوشمند مجدداً فعال گردید.",
- "upvotes": 85,
- "days_ago": 4,
- "resolved_at": (now - timedelta(days=2)).strftime("%Y-%m-%d %H:%M"),
- "timeline": [
- ("pending", "ثبت گزارش خرابی علائم ترافیکی", "گزارش دریافت گردید."),
- ("reviewing", "ارجاع به شرکت کنترل ترافیک", "تایید خرابی سختافزاری کنترلر."),
- ("in_progress", "تعویض برد فرمان و سنسورها", "عملیات فنی در محل انجام شد."),
- ("resolved", "راهاندازی و اتصال به مرکز کنترل", "چراغ طبق برنامه زمانبندی هوشمند شروع به کار کرد.")
- ],
- "comments": [
- ("سامان یوسفی", "دست مریزاد، امروز صبح ترافیک خیلی روانتر بود.")
- ]
- }
- ]
-
- for item in sample_issues:
- created_time = (now - timedelta(days=item["days_ago"], hours=random.randint(1, 10))).strftime("%Y-%m-%d %H:%M")
- cursor.execute("""
- INSERT INTO issues (
- tracking_code, title, category, description, address, district, priority, status,
- lat, lng, image_url, resolved_image_url, reporter_name, reporter_phone,
- official_response, upvotes, created_at, resolved_at
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """, (
- item["tracking_code"], item["title"], item["category"], item["description"],
- item["address"], item["district"], item["priority"], item["status"],
- item["lat"], item["lng"], item["image_url"], item["resolved_image_url"],
- item["reporter_name"], item["reporter_phone"], item["official_response"],
- item["upvotes"], created_time, item["resolved_at"]
- ))
-
- issue_id = cursor.lastrowid
-
- for tl_status, tl_title, tl_desc in item["timeline"]:
- cursor.execute("""
- INSERT INTO timeline (issue_id, status, title, description, created_at)
- VALUES (?, ?, ?, ?, ?)
- """, (issue_id, tl_status, tl_title, tl_desc, created_time))
-
- for author, comment_text in item["comments"]:
- cursor.execute("""
- INSERT INTO comments (issue_id, author_name, content, created_at)
- VALUES (?, ?, ?, ?)
- """, (issue_id, author, comment_text, created_time))
diff --git a/db.php b/db.php
index fcc524b..8685c9e 100644
--- a/db.php
+++ b/db.php
@@ -1,273 +1,299 @@
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
- return $pdo;
-}
+ public static function getConnection(): PDO {
+ if (self::$pdo === null) {
+ $isNew = !file_exists(self::$dbFile);
+ self::$pdo = new PDO('sqlite:' . self::$dbFile);
+ self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+
+ // Enable WAL mode for high concurrency and performance
+ self::$pdo->exec('PRAGMA journal_mode = WAL;');
+ self::$pdo->exec('PRAGMA foreign_keys = ON;');
-function initDB() {
- $pdo = getDB();
+ self::initializeSchema();
- // Create Issues Table
- $pdo->exec("CREATE TABLE IF NOT EXISTS issues (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- tracking_code TEXT UNIQUE NOT NULL,
- title TEXT NOT NULL,
- category TEXT NOT NULL,
- description TEXT NOT NULL,
- address TEXT NOT NULL,
- district INTEGER NOT NULL,
- priority TEXT NOT NULL,
- status TEXT NOT NULL,
- lat REAL NOT NULL,
- lng REAL NOT NULL,
- image_url TEXT,
- resolved_image_url TEXT,
- reporter_name TEXT,
- reporter_phone TEXT,
- official_response TEXT,
- upvotes INTEGER DEFAULT 0,
- created_at TEXT NOT NULL,
- resolved_at TEXT
- )");
-
- // Create Comments Table
- $pdo->exec("CREATE TABLE IF NOT EXISTS comments (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- issue_id INTEGER NOT NULL,
- author_name TEXT NOT NULL,
- content TEXT NOT NULL,
- created_at TEXT NOT NULL,
- FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
- )");
-
- // Create Timeline Table
- $pdo->exec("CREATE TABLE IF NOT EXISTS timeline (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- issue_id INTEGER NOT NULL,
- status TEXT NOT NULL,
- title TEXT NOT NULL,
- description TEXT,
- created_at TEXT NOT NULL,
- FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
- )");
-
- // Check count and seed if empty
- $stmt = $pdo->query("SELECT COUNT(*) FROM issues");
- $count = (int)$stmt->fetchColumn();
-
- if ($count === 0) {
- seedInitialData($pdo);
+ if ($isNew || self::isAccountTableEmpty()) {
+ self::seedInitialData();
+ }
+ }
+ return self::$pdo;
}
-}
-function seedInitialData(PDO $pdo) {
- $now = new DateTime();
+ private static function initializeSchema(): void {
+ $db = self::$pdo;
- $sampleIssues = [
- [
- 'tracking_code' => 'SHR-78214',
- 'title' => 'فرونشست و چاله عمیق در لاین کندرو',
- 'category' => 'آسفالت و معابر',
- 'description' => 'وجود چاله عمیق با قطر حدود ۱ متر پس از بارندگی اخیر که باعث خسارت به لاستیک خودروها و ترمزهای ناگهانی خطرناک میشود.',
- 'address' => 'بزرگراه شهید همت، نرسیده به خروجی گاندی، لاین کندرو',
- 'district' => 3,
- 'priority' => 'emergency',
- 'status' => 'in_progress',
- 'lat' => 35.7538,
- 'lng' => 51.4172,
- 'image_url' => 'https://images.unsplash.com/photo-1515162816999-a0c47dc192f7?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => null,
- 'reporter_name' => 'علی رضایی',
- 'reporter_phone' => '09121112233',
- 'official_response' => 'اکیپ آسفالتریزی ناحیه ۲ شهرداری منطقه ۳ اعزام گردیده و عملیات زیرسازی و لکهگیری در حال انجام است.',
- 'upvotes' => 42,
- 'days_ago' => 2,
- 'resolved_at' => null,
- 'timeline' => [
- ['pending', 'ثبت گزارش توسط شهروند', 'گزارش در سامانه ۱۳۷ با موفقیت ثبت شد.'],
- ['reviewing', 'تایید کارشناس و ارجاع به معاونت فنی و عمران', 'موضوع به ناحیه ۲ ارجاع داده شد.'],
- ['in_progress', 'اعزام اکیپ عملیاتی لکهگیری', 'تیم فنی با تجهیزات در محل مستقر شد.']
- ],
- 'comments' => [
- ['سارا محمدی', 'دیروز لاستیک ماشین من هم اینجا آسیب دید، ممنون که پیگیری میکنید.'],
- ['حسین کاظمی', 'امیدوارم امشب تا قبل از ترافیک صبحگاهی تموم بشه.']
- ]
- ],
- [
- 'tracking_code' => 'SHR-65109',
- 'title' => 'خاموشی کامل پایه چراغهای روشنایی بوستان',
- 'category' => 'روشنایی و برق',
- 'description' => 'کل مسیر پیادهروی شرقی بوستان ملت در تاریکی مطلق است که باعث کاهش امنیت خانوادهها و ورزشکاران شبانه شده است.',
- 'address' => 'خیابان ولیعصر، بوستان ملت، ضلع شرقی جنب دریاچه',
- 'district' => 3,
- 'priority' => 'high',
- 'status' => 'resolved',
- 'lat' => 35.7801,
- 'lng' => 51.4116,
- 'image_url' => 'https://images.unsplash.com/photo-1509114397022-ed747cca3f65?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => 'https://images.unsplash.com/photo-1517457373958-b7bdd4587205?auto=format&fit=crop&w=800&q=80',
- 'reporter_name' => 'مریم احمدی',
- 'reporter_phone' => '09123334455',
- 'official_response' => 'کابلکشی زیرزمینی و تعویض پروژکتورهای معیوب توسط اداره زیباسازی و تاسیسات منطقه انجام و روشنایی کامل برقرار گردید.',
- 'upvotes' => 68,
- 'days_ago' => 5,
- 'resolved_at' => (clone $now)->modify('-1 day')->format('Y-m-d H:i'),
- 'timeline' => [
- ['pending', 'ثبت گزارش توسط شهروند', 'گزارش دریافت شد.'],
- ['reviewing', 'بررسی میدانی اداره تاسیسات', 'نقص کابل اصلی تایید شد.'],
- ['in_progress', 'تعمیرات و تعویض کابل', 'تیم تاسیسات در حال اجرای خط جدید.'],
- ['resolved', 'تکمیل و رفع نقص روشنایی', 'چراغها روشن و مدار به طور کامل تست شد.']
- ],
- 'comments' => [
- ['امیر رستمی', 'خیلی سریع درستش کردن، واقعا تشکر از عوامل شهرداری.'],
- ['نگار توکلی', 'دیشب رفتم پارک، همه چراغها درست شده بود.']
- ]
- ],
- [
- 'tracking_code' => 'SHR-91203',
- 'title' => 'انباشت پسماند و سرریز مخزن زباله شهری',
- 'category' => 'نظافت و پسماند',
- 'description' => 'مخزن مکانیزه زباله شکسته شده و زبالهها در پیادهرو پخش شده که باعث بوی نامطبوع و تجمع حشرات شده است.',
- 'address' => 'خیابان آزادی، تقاطع خیابان استاد معین، پلاک ۴۲',
- 'district' => 9,
- 'priority' => 'medium',
- 'status' => 'pending',
- 'lat' => 35.6997,
- 'lng' => 51.3486,
- 'image_url' => 'https://images.unsplash.com/photo-1532996122724-e3c354a0b15b?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => null,
- 'reporter_name' => 'سعید کریمی',
- 'reporter_phone' => '09355556677',
- 'official_response' => 'گزارش در نوبت بررسی ناظر پسماند منطقه قرار گرفته است.',
- 'upvotes' => 19,
- 'days_ago' => 1,
- 'resolved_at' => null,
- 'timeline' => [
- ['pending', 'ثبت گزارش در سامانه', 'منتظر بازرسی و تخصیص سطل جدید مکانیزه.']
- ],
- 'comments' => [
- ['مهدی بهرامی', 'لطفا سطلهای پدالدار بگذارید که درش بسته بمونه.']
- ]
- ],
- [
- 'tracking_code' => 'SHR-44390',
- 'title' => 'آسیب دیدگی و شکستن شاخههای درخت کهنسال بر اثر طوفان',
- 'category' => 'فضای سبز و بوستانها',
- 'description' => 'شاخههای سنگین درخت چنار شکسته و روی سیمهای برق و پیادهرو معلق مانده است، احتمال سقوط روی عابرین وجود دارد.',
- 'address' => 'خیابان شریعتی، بالاتر از پل رومی، نبش کوچه یاس',
- 'district' => 1,
- 'priority' => 'emergency',
- 'status' => 'in_progress',
- 'lat' => 35.7985,
- 'lng' => 51.4332,
- 'image_url' => 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => null,
- 'reporter_name' => 'پویا سرمدی',
- 'reporter_phone' => '09124445566',
- 'official_response' => 'اکیپ سازمان بوستانها و آتشنشانی با جرثقیل در حال هرس ایمن و رفع خطر هستند.',
- 'upvotes' => 54,
- 'days_ago' => 1,
- 'resolved_at' => null,
- 'timeline' => [
- ['pending', 'ثبت فوری گزارش خطر', 'هشدار سقوط شاخه دریافت شد.'],
- ['reviewing', 'هماهنگی با سازمان آتشنشانی و فضای سبز', 'اعلام وضعیت اضطراری.'],
- ['in_progress', 'عملیات رفع خطر و هرس', 'حصارکشی پیادهرو و استقرار بالابر.']
- ],
- 'comments' => [
- ['رویا صبوری', 'خدا رو شکر سریع اومدن نوار خطر کشیدن.']
- ]
- ],
- [
- 'tracking_code' => 'SHR-33219',
- 'title' => 'سد معبر طولانی مصالح ساختمانی و تخریب پیادهرو',
- 'category' => 'ساختمانسازی و سد معبر',
- 'description' => 'یک پروژه ساختمانی بیش از سه هفته است که تمام پیادهرو و بخشی از خیابان را با نخاله و داربست بدون راه عبور ایمن مسدود کرده است.',
- 'address' => 'سعادتآباد، میدان کاج، خیابان مروارید، پلاک ۱۸',
- 'district' => 2,
- 'priority' => 'high',
- 'status' => 'reviewing',
- 'lat' => 35.7794,
- 'lng' => 51.3758,
- 'image_url' => 'https://images.unsplash.com/photo-1541888946425-d0fbb18086f6?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => null,
- 'reporter_name' => 'فرشید نادری',
- 'reporter_phone' => '09127778899',
- 'official_response' => 'اخطاریه ماده ۱۰۰ و رفع سد معبر برای مالک صادر شد. مهلت رفع ۴۸ ساعت میباشد.',
- 'upvotes' => 37,
- 'days_ago' => 3,
- 'resolved_at' => null,
- 'timeline' => [
- ['pending', 'ثبت گزارش تخلف ساختمانی', 'ثبت شد.'],
- ['reviewing', 'بازدید مامور اجرای احکام شهرداری', 'صدور اخطاریه رسمی رفع انسداد معبر.']
- ],
- 'comments' => [
- ['کامران شمس', 'مادر من با ویلچر مجبور شد بره توی خیابان که خیلی خطرناک بود!']
- ]
- ],
- [
- 'tracking_code' => 'SHR-55821',
- 'title' => 'خرابی و چشمکزن ماندن چراغ راهنمایی تقاطع پرتردد',
- 'category' => 'ترافیک و حملونقل',
- 'description' => 'چراغ راهنمایی تقاطع به مدت دو روز چشمکزن زرد مانده که باعث گره کور ترافیکی و تصادفات مکرر شده است.',
- 'address' => 'میدان فاطمی (جهاد)، تقاطع خیابان جویبار و شهید گمنام',
- 'district' => 6,
- 'priority' => 'high',
- 'status' => 'resolved',
- 'lat' => 35.7208,
- 'lng' => 51.4082,
- 'image_url' => 'https://images.unsplash.com/photo-1517649763962-0c623266ddc0?auto=format&fit=crop&w=800&q=80',
- 'resolved_image_url' => 'https://images.unsplash.com/photo-1508873696983-2df5703bc275?auto=format&fit=crop&w=800&q=80',
- 'reporter_name' => 'ندا زمانی',
- 'reporter_phone' => '09191113355',
- 'official_response' => 'برد الکترونیکی کنترلر چراغ توسط شرکت کنترل ترافیک تهران تعویض و زمانبندی هوشمند مجدداً فعال گردید.',
- 'upvotes' => 85,
- 'days_ago' => 4,
- 'resolved_at' => (clone $now)->modify('-2 days')->format('Y-m-d H:i'),
- 'timeline' => [
- ['pending', 'ثبت گزارش خرابی علائم ترافیکی', 'گزارش دریافت گردید.'],
- ['reviewing', 'ارجاع به شرکت کنترل ترافیک', 'تایید خرابی سختافزاری کنترلر.'],
- ['in_progress', 'تعویض برد فرمان و سنسورها', 'عملیات فنی در محل انجام شد.'],
- ['resolved', 'راهاندازی و اتصال به مرکز کنترل', 'چراغ طبق برنامه زمانبندی هوشمند شروع به کار کرد.']
- ],
- 'comments' => [
- ['سامان یوسفی', 'دست مریزاد، امروز صبح ترافیک خیلی روانتر بود.']
- ]
- ]
- ];
+ // 1. Trading Accounts Table
+ $db->exec("
+ CREATE TABLE IF NOT EXISTS accounts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ broker TEXT DEFAULT 'IC Markets',
+ account_number TEXT DEFAULT '',
+ currency TEXT DEFAULT 'USD',
+ initial_balance REAL NOT NULL DEFAULT 10000.0,
+ leverage REAL DEFAULT 100.0,
+ account_type TEXT DEFAULT 'Real', -- Real, Demo, PropFirm
+ is_default INTEGER DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ ");
- $insertIssue = $pdo->prepare("INSERT INTO issues (
- tracking_code, title, category, description, address, district, priority, status,
- lat, lng, image_url, resolved_image_url, reporter_name, reporter_phone,
- official_response, upvotes, created_at, resolved_at
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ // 2. Trades Table
+ $db->exec("
+ CREATE TABLE IF NOT EXISTS trades (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ account_id INTEGER NOT NULL,
+ ticket TEXT DEFAULT '',
+ symbol TEXT NOT NULL, -- EURUSD, XAUUSD, GBPUSD, etc.
+ trade_type TEXT NOT NULL, -- buy, sell
+ lot_size REAL NOT NULL DEFAULT 0.1,
+ open_price REAL NOT NULL,
+ close_price REAL DEFAULT NULL,
+ stop_loss REAL DEFAULT NULL,
+ take_profit REAL DEFAULT NULL,
+ open_time DATETIME NOT NULL,
+ close_time DATETIME DEFAULT NULL,
+ pips REAL DEFAULT 0.0,
+ profit REAL DEFAULT 0.0, -- Net P&L in currency ($)
+ commission REAL DEFAULT 0.0,
+ swap REAL DEFAULT 0.0,
+ status TEXT DEFAULT 'closed', -- open, closed
+ strategy TEXT DEFAULT 'Price Action',
+ timeframe TEXT DEFAULT 'M15',
+ session TEXT DEFAULT 'London', -- London, New York, Asian, Overlap
+ risk_reward REAL DEFAULT NULL,
+ risk_percent REAL DEFAULT 1.0,
+ emotion TEXT DEFAULT 'Disciplined', -- Disciplined, Greedy, Fearful, Revenge, Confident, Impatient, Neutral
+ entry_notes TEXT DEFAULT '',
+ exit_notes TEXT DEFAULT '',
+ lessons TEXT DEFAULT '',
+ screenshot_entry TEXT DEFAULT '',
+ screenshot_exit TEXT DEFAULT '',
+ tags TEXT DEFAULT '',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
+ );
+ ");
- $insertTimeline = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, ?, ?, ?, ?)");
- $insertComment = $pdo->prepare("INSERT INTO comments (issue_id, author_name, content, created_at) VALUES (?, ?, ?, ?)");
+ // 3. Daily Notes / Journal Mindset Log
+ $db->exec("
+ CREATE TABLE IF NOT EXISTS daily_logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ account_id INTEGER NOT NULL,
+ log_date DATE NOT NULL,
+ notes TEXT DEFAULT '',
+ mindset_score INTEGER DEFAULT 5, -- 1 to 5
+ market_condition TEXT DEFAULT 'Normal',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE,
+ UNIQUE(account_id, log_date)
+ );
+ ");
- foreach ($sampleIssues as $item) {
- $createdTime = (clone $now)->modify("-{$item['days_ago']} days")->format('Y-m-d H:i');
- $insertIssue->execute([
- $item['tracking_code'], $item['title'], $item['category'], $item['description'],
- $item['address'], $item['district'], $item['priority'], $item['status'],
- $item['lat'], $item['lng'], $item['image_url'], $item['resolved_image_url'],
- $item['reporter_name'], $item['reporter_phone'], $item['official_response'],
- $item['upvotes'], $createdTime, $item['resolved_at']
+ // 4. Strategies / Setups Table
+ $db->exec("
+ CREATE TABLE IF NOT EXISTS strategies (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL UNIQUE,
+ description TEXT DEFAULT '',
+ color TEXT DEFAULT '#3b82f6',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ ");
+
+ // Create indexes for fast analytical aggregations
+ $db->exec("CREATE INDEX IF NOT EXISTS idx_trades_account ON trades(account_id);");
+ $db->exec("CREATE INDEX IF NOT EXISTS idx_trades_open_time ON trades(open_time);");
+ $db->exec("CREATE INDEX IF NOT EXISTS idx_trades_close_time ON trades(close_time);");
+ $db->exec("CREATE INDEX IF NOT EXISTS idx_trades_symbol ON trades(symbol);");
+ $db->exec("CREATE INDEX IF NOT EXISTS idx_trades_status ON trades(status);");
+ }
+
+ private static function isAccountTableEmpty(): bool {
+ $stmt = self::$pdo->query("SELECT COUNT(*) FROM accounts");
+ return ((int)$stmt->fetchColumn()) === 0;
+ }
+
+ public static function seedInitialData(): void {
+ $db = self::$pdo;
+
+ // Default Strategies
+ $strategies = [
+ ['Price Action', 'Break of structure, support & resistance', '#3b82f6'],
+ ['ICT / SMC', 'Order blocks, Fair Value Gaps (FVG) and liquidity sweeps', '#8b5cf6'],
+ ['Trend Following', 'Moving average pullbacks and trend continuation', '#10b981'],
+ ['Scalping', 'Fast momentum moves on M1/M5 charts', '#f59e0b'],
+ ['Reversal / Divergence', 'RSI/MACD divergence at key supply/demand levels', '#ec4899'],
+ ['News Trading', 'High-impact economic news releases', '#ef4444'],
+ ];
+
+ $stmtStrat = $db->prepare("INSERT OR IGNORE INTO strategies (name, description, color) VALUES (?, ?, ?)");
+ foreach ($strategies as $strat) {
+ $stmtStrat->execute($strat);
+ }
+
+ // Default Accounts
+ $stmtAcc = $db->prepare("
+ INSERT INTO accounts (name, broker, account_number, currency, initial_balance, leverage, account_type, is_default)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ ");
+ $stmtAcc->execute(['حساب معاملاتی اصلی (Main Live)', 'IC Markets', '8841920', 'USD', 10000.0, 100.0, 'Real', 1]);
+ $mainAccountId = (int)$db->lastInsertId();
+
+ $stmtAcc->execute(['چالش پراپ فرم ۱۰۰ هزار دلاری (FTMO Prop)', 'FTMO', '2948173', 'USD', 100000.0, 100.0, 'PropFirm', 0]);
+
+ // Seed rich realistic trading history for main account
+ self::generateSampleTrades($mainAccountId, 10000.0);
+ }
+
+ public static function generateSampleTrades(int $accountId, float $startBalance = 10000.0): void {
+ $db = self::$pdo;
+
+ // Trade definition array:
+ // [0:symbol, 1:type, 2:lot, 3:openPrice, 4:closePrice, 5:sl, 6:tp, 7:pips, 8:profit, 9:comm, 10:swap, 11:strat, 12:tf, 13:session, 14:rr, 15:emotion, 16:entryNotes, 17:exitNotes, 18:lessons]
+ $tradeDataList = [
+ ['EURUSD', 'buy', 1.0, 1.08200, 1.08650, 1.07950, 1.08700, 45.0, 450.0, -7.0, 0.0, 'ICT / SMC', 'H1', 'London', 1.8, 'Disciplined', 'جاروب نقدینگی لندن و واکنش به FVG', 'رسیدن به هدف نقدینگی سقف روز قبل', 'پایبندی به استراتژی'],
+ ['XAUUSD', 'buy', 0.5, 2350.20, 2368.50, 2342.00, 2370.00, 183.0, 915.0, -5.0, -2.1, 'Price Action', 'M15', 'New York', 2.2, 'Confident', 'تست مجدد سطح حمایتی ۲۳۵۰ با کندل تاییدیه پین بار', 'خروج در نزدیکی تارگت با سود عالی', 'صبر برای پولبک کلید موفقیت بود'],
+ ['GBPUSD', 'sell', 1.2, 1.27600, 1.27150, 1.27850, 1.27000, 45.0, 540.0, -8.4, 1.5, 'Trend Following', 'M15', 'London', 1.8, 'Disciplined', 'شکست خط روند نزولی و پولبک به EMA50', 'ریسک فری شد و در تارگت ۲ بسته شد', 'مدیریت ترید عالی'],
+ ['USDJPY', 'buy', 1.0, 154.800, 154.450, 154.400, 155.600, -35.0, -225.0, -7.0, 0.0, 'Scalping', 'M5', 'Asian', 0.0, 'Fearful', 'ورود با مومنتوم صعودی ضعیف در نشست آسیا', 'اصابت به استاپ لاس به دلیل اسپرد بالا', 'در سشن آسیا نباید روی جفتهای کمنوسان بدون کاتالیزور معامله کرد'],
+ ['EURUSD', 'sell', 1.5, 1.08900, 1.08350, 1.09150, 1.08200, 55.0, 825.0, -10.5, 0.0, 'ICT / SMC', 'M15', 'New York', 2.2, 'Disciplined', 'شکار نقدینگی سشن نیویورک و تایید با اردر بلاک خرسی', 'کست کامل سود در کف سشن لندن', 'ورود دقیق روی اوردربلاک'],
+ ['XAUUSD', 'sell', 0.8, 2385.00, 2392.50, 2392.00, 2370.00, -75.0, -600.0, -8.0, 0.0, 'Reversal / Divergence', 'M15', 'New York', 0.0, 'Greedy', 'ورود برخلاف روند قدرتمند بدون واگرایی قطعی', 'استاپ خورد، طلا شتاب شدید صعودی داشت', 'هرگز نباید جلوی قطار سریعالسیر ایستاد!'],
+ ['BTCUSD', 'buy', 0.2, 63200.0, 65100.0, 62400.0, 65500.0, 1900.0, 380.0, -3.0, -0.5, 'Trend Following', 'H4', 'London', 2.4, 'Confident', 'حفظ حمایت رند ۶۳۰۰۰ و تشکیل الگوی دابل باتم', 'خروج پلهای در ۶۵۱۰۰', 'معامله سوینگی تمیز'],
+ ['US30', 'buy', 0.5, 39200.0, 39650.0, 39000.0, 39700.0, 450.0, 225.0, -5.0, 0.0, 'Scalping', 'M5', 'New York', 2.2, 'Disciplined', 'اوپن سشن نیویورک و جهش شاخص داوجونز', 'خروج سریع با تارگت تعیین شده', 'مدیریت ریسک عالی'],
+ ['GBPUSD', 'buy', 1.0, 1.26800, 1.27400, 1.26500, 1.27500, 60.0, 600.0, -7.0, 0.0, 'ICT / SMC', 'M15', 'London', 2.0, 'Disciplined', 'برگشت قیمت از ناحیه تقاضا و جاروب کف آسیا', 'تارگت لمس شد', 'برنامه معاملاتی مو به مو اجرا شد'],
+ ['EURUSD', 'sell', 1.0, 1.08500, 1.08800, 1.08800, 1.08000, -30.0, -300.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 0.0, 'Impatient', 'ورود عجولانه قبل از بسته شدن کندل ۱۵ دقیقه', 'برگشت قیمت به بالا و استاپ', 'صبر برای کلوز کندل ضروری است'],
+ ['XAUUSD', 'buy', 0.7, 2360.00, 2378.00, 2353.00, 2380.00, 180.0, 1260.0, -7.0, -1.8, 'ICT / SMC', 'H1', 'Overlap', 2.5, 'Disciplined', 'تلاقی FVG با لول ۵۰٪ فیبوناچی در اورلپ لندن/نیویورک', 'تارگت کامل محقق شد', 'یکی از بهترین معاملات ماه'],
+ ['AUDUSD', 'sell', 1.0, 0.66800, 0.66350, 0.67050, 0.66200, 45.0, 450.0, -7.0, 1.2, 'Trend Following', 'H1', 'Asian', 1.8, 'Neutral', 'شکست رنج قیمتی و تاییدیه با دادههای اشتغال استرالیا', 'خروج با سود خوب', 'انطباق خوب با تحلیل فاندامنتال'],
+ ['USDJPY', 'sell', 1.0, 156.200, 155.300, 156.600, 155.000, 90.0, 580.0, -7.0, 0.0, 'Price Action', 'H1', 'New York', 2.2, 'Disciplined', 'سقف دو قلو در تایم فریم یک ساعته با کندل اینگالف نزولی', 'خروج در حمایت کلیدی ۱۵۵.۳۰', 'معامله بسیار روان و بدون استرس'],
+ ['EURUSD', 'buy', 1.2, 1.08300, 1.08100, 1.08050, 1.08900, -20.0, -240.0, -8.4, 0.0, 'Scalping', 'M5', 'London', 0.0, 'Neutral', 'تلاش برای گرفتن بریک اوت سریع که فیک اوت شد', 'حد ضرر نجات داد', 'ضرر کوچک و کنترلشده'],
+ ['XAUUSD', 'buy', 0.6, 2375.00, 2394.00, 2368.00, 2395.00, 190.0, 1140.0, -6.0, 0.0, 'ICT / SMC', 'M15', 'New York', 2.7, 'Confident', 'سوییپ نقدینگی کف روزانه در اخبار CPI و واکنش انفجاری', 'خروج در تارگت ۲۳۹۴', 'مدیریت پوزیشن عالی حین خبر'],
+ ['GBPUSD', 'sell', 1.0, 1.27500, 1.27100, 1.27800, 1.27000, 40.0, 400.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 1.3, 'Disciplined', 'برخورد به سقف کانال نزولی', 'برداشت سود مطمئن', 'معامله استاندارد طبق پلن'],
+ ['BTCUSD', 'sell', 0.25, 66500.0, 64200.0, 67200.0, 64000.0, 2300.0, 575.0, -3.5, 0.0, 'Reversal / Divergence', 'H4', 'New York', 3.2, 'Disciplined', 'واگرایی منفی مشهود در RSI تایم ۴ ساعته', 'ریزش عالی بیتکوین و سیو سود', 'نسبت ریسک به ریوارد بالای ۳'],
+ ['US30', 'sell', 0.4, 39800.0, 39950.0, 39950.0, 39400.0, -150.0, -60.0, -4.0, 0.0, 'Scalping', 'M5', 'New York', 0.0, 'Neutral', 'ترید خلاف ترند در اوپن بازار', 'استاپ کوچک خورد', 'مدیریت ریسک عالی'],
+ ['EURUSD', 'buy', 1.0, 1.08400, 1.08920, 1.08150, 1.09000, 52.0, 520.0, -7.0, 0.0, 'ICT / SMC', 'H1', 'London', 2.1, 'Disciplined', 'پر شدن FVG سشن لندن و چرخش روند', 'تارگت لمس شد', 'آرامش و صبر کامل'],
+ ['XAUUSD', 'buy', 0.5, 2390.00, 2415.00, 2382.00, 2420.00, 250.0, 1250.0, -5.0, -2.5, 'Trend Following', 'H4', 'New York', 3.1, 'Confident', 'شکست سقف تاریخی قبلی و پولبک استاندارد', 'سود فوقالعاده با تارگت بالای ۲۴۱۵', 'رعایت تمام اصول استراتژی'],
+ ['USDJPY', 'buy', 1.0, 155.100, 155.650, 154.750, 155.800, 55.0, 355.0, -7.0, 0.0, 'Price Action', 'M15', 'Asian', 1.5, 'Disciplined', 'حمایت معتبر در سشن توکیو', 'خروج با سود خوب در شروع سشن لندن', 'ترید تمیز'],
+ ['GBPUSD', 'buy', 1.0, 1.26900, 1.26600, 1.26600, 1.27600, -30.0, -300.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 0.0, 'Fearful', 'انتشار دادههای ضعیف بریتانیا و استاپ خوردن', 'استاپ رعایت شد', 'پایبندی به استاپ لاس نجاتبخش بود'],
+ ['EURUSD', 'sell', 1.0, 1.08800, 1.08300, 1.09050, 1.08200, 50.0, 500.0, -7.0, 0.0, 'ICT / SMC', 'M15', 'New York', 2.0, 'Disciplined', 'شکار نقدینگی خریداران بالای ۱.۰۸۸۰ و ریزش سریع', 'خروج در کف روز', 'ستاپ کاملاً مکانیکی و تست شده']
+ ];
+
+ $stmtTrade = $db->prepare("
+ INSERT INTO trades (
+ account_id, ticket, symbol, trade_type, lot_size,
+ open_price, close_price, stop_loss, take_profit,
+ open_time, close_time, pips, profit, commission, swap,
+ status, strategy, timeframe, session, risk_reward, emotion,
+ entry_notes, exit_notes, lessons, tags
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ");
+
+ $baseTime = time() - (count($tradeDataList) * 86400 * 0.9);
+ $ticketBase = 7418290;
+
+ foreach ($tradeDataList as $i => $t) {
+ $ticket = (string)($ticketBase + $i * 17);
+ $openTs = $baseTime + ($i * 75000) + rand(1000, 15000);
+ $durationSec = rand(1800, 28800);
+ $closeTs = $openTs + $durationSec;
+
+ $openTimeStr = date('Y-m-d H:i:s', $openTs);
+ $closeTimeStr = date('Y-m-d H:i:s', $closeTs);
+
+ $stmtTrade->execute([
+ $accountId,
+ $ticket,
+ $t[0],
+ $t[1],
+ $t[2],
+ $t[3],
+ $t[4],
+ $t[5],
+ $t[6],
+ $openTimeStr,
+ $closeTimeStr,
+ $t[7],
+ $t[8],
+ $t[9],
+ $t[10],
+ 'closed',
+ $t[11],
+ $t[12],
+ $t[13],
+ $t[14],
+ $t[15],
+ $t[16],
+ $t[17],
+ $t[18],
+ 'Live,DayTrade,Forex'
+ ]);
+ }
+
+ // Add 2 open trades
+ $stmtTrade->execute([
+ $accountId,
+ (string)($ticketBase + 991),
+ 'XAUUSD',
+ 'buy',
+ 0.5,
+ 2405.50,
+ null,
+ 2396.00,
+ 2425.00,
+ date('Y-m-d H:i:s', time() - 3600),
+ null,
+ 45.0,
+ 225.0,
+ -5.0,
+ 0.0,
+ 'open',
+ 'ICT / SMC',
+ 'H1',
+ 'London',
+ 2.0,
+ 'Disciplined',
+ 'ورود پس از شکست مقاومت ۲۴۰۰ و تثبیت بالای آن',
+ '',
+ '',
+ 'OpenTrade,Swing'
]);
- $issueId = $pdo->lastInsertId();
- foreach ($item['timeline'] as $tl) {
- $insertTimeline->execute([$issueId, $tl[0], $tl[1], $tl[2], $createdTime]);
- }
-
- foreach ($item['comments'] as $cm) {
- $insertComment->execute([$issueId, $cm[0], $cm[1], $createdTime]);
- }
+ $stmtTrade->execute([
+ $accountId,
+ (string)($ticketBase + 992),
+ 'EURUSD',
+ 'sell',
+ 1.0,
+ 1.08620,
+ null,
+ 1.08850,
+ 1.08100,
+ date('Y-m-d H:i:s', time() - 7200),
+ null,
+ 12.0,
+ 120.0,
+ -7.0,
+ 0.0,
+ 'open',
+ 'Price Action',
+ 'M15',
+ 'London',
+ 2.2,
+ 'Confident',
+ 'تشکیل الگوی پوشای نزولی در لول مقاومت روزانه',
+ '',
+ '',
+ 'OpenTrade,Intraday'
+ ]);
}
}
diff --git a/forex_journal.db b/forex_journal.db
new file mode 100644
index 0000000..84020d4
Binary files /dev/null and b/forex_journal.db differ
diff --git a/index.php b/index.php
index 7246437..13181e2 100644
--- a/index.php
+++ b/index.php
@@ -1,3 +1,3 @@
0 else 0, 1)
- }
-
-@app.get("/api/issues")
-def list_issues(
- category: Optional[str] = None,
- status: Optional[str] = None,
- district: Optional[int] = None,
- priority: Optional[str] = None,
- search: Optional[str] = None,
- sort_by: Optional[str] = "newest"
-):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- query = "SELECT * FROM issues WHERE 1=1"
- params = []
-
- if category and category != "all":
- query += " AND category = ?"
- params.append(category)
-
- if status and status != "all":
- query += " AND status = ?"
- params.append(status)
-
- if district and district > 0:
- query += " AND district = ?"
- params.append(district)
-
- if priority and priority != "all":
- query += " AND priority = ?"
- params.append(priority)
-
- if search:
- query += " AND (title LIKE ? OR description LIKE ? OR address LIKE ? OR tracking_code LIKE ?)"
- s_pattern = f"%{search}%"
- params.extend([s_pattern, s_pattern, s_pattern, s_pattern])
-
- if sort_by == "upvotes":
- query += " ORDER BY upvotes DESC, id DESC"
- elif sort_by == "oldest":
- query += " ORDER BY id ASC"
- elif sort_by == "emergency":
- query += " ORDER BY CASE priority WHEN 'emergency' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END, id DESC"
- else: # newest
- query += " ORDER BY id DESC"
-
- rows = cursor.execute(query, params).fetchall()
-
- issues = []
- for r in rows:
- item = dict(r)
- # Fetch comment count
- c_count = cursor.execute("SELECT COUNT(*) FROM comments WHERE issue_id = ?", (r["id"],)).fetchone()[0]
- item["comment_count"] = c_count
- issues.append(item)
-
- conn.close()
- return issues
-
-@app.get("/api/issues/{issue_id}")
-def get_issue(issue_id: int):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- issue_row = cursor.execute("SELECT * FROM issues WHERE id = ?", (issue_id,)).fetchone()
- if not issue_row:
- conn.close()
- raise HTTPException(status_code=404, detail="گزارش مورد نظر یافت نشد.")
-
- issue = dict(issue_row)
-
- # Timeline
- timeline_rows = cursor.execute("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC", (issue_id,)).fetchall()
- issue["timeline"] = [dict(t) for t in timeline_rows]
-
- # Comments
- comment_rows = cursor.execute("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC", (issue_id,)).fetchall()
- issue["comments"] = [dict(c) for c in comment_rows]
-
- conn.close()
- return issue
-
-@app.get("/api/track/{tracking_code}")
-def track_issue(tracking_code: str):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- code = tracking_code.strip().upper()
- issue_row = cursor.execute("SELECT * FROM issues WHERE UPPER(tracking_code) = ?", (code,)).fetchone()
- if not issue_row:
- conn.close()
- raise HTTPException(status_code=404, detail=f"هیچ گزارشی با کد رهگیری «{tracking_code}» یافت نشد.")
-
- issue_id = issue_row["id"]
- issue = dict(issue_row)
-
- timeline_rows = cursor.execute("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC", (issue_id,)).fetchall()
- issue["timeline"] = [dict(t) for t in timeline_rows]
-
- comment_rows = cursor.execute("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC", (issue_id,)).fetchall()
- issue["comments"] = [dict(c) for c in comment_rows]
-
- conn.close()
- return issue
-
-@app.post("/api/issues")
-def create_issue(payload: IssueCreate):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- # Generate unique tracking code
- rand_num = random.randint(10000, 99999)
- tracking_code = f"SHR-{rand_num}"
-
- now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
-
- cursor.execute("""
- INSERT INTO issues (
- tracking_code, title, category, description, address, district, priority, status,
- lat, lng, image_url, reporter_name, reporter_phone, upvotes, created_at
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?, 1, ?)
- """, (
- tracking_code, payload.title, payload.category, payload.description, payload.address,
- payload.district, payload.priority, payload.lat, payload.lng, payload.image_url,
- payload.reporter_name or "شهروند", payload.reporter_phone or "", now_str
- ))
-
- issue_id = cursor.lastrowid
-
- # Add initial timeline event
- cursor.execute("""
- INSERT INTO timeline (issue_id, status, title, description, created_at)
- VALUES (?, 'pending', 'ثبت اولیه گزارش در سامانه', 'گزارش شما با موفقیت در سامانه شهرنگار ثبت گردید و در نوبت بررسی کارشناسی قرار گرفت.', ?)
- """, (issue_id, now_str))
-
- conn.commit()
-
- created = cursor.execute("SELECT * FROM issues WHERE id = ?", (issue_id,)).fetchone()
- res = dict(created)
- conn.close()
-
- return res
-
-@app.post("/api/issues/{issue_id}/upvote")
-def upvote_issue(issue_id: int):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- cursor.execute("UPDATE issues SET upvotes = upvotes + 1 WHERE id = ?", (issue_id,))
- if cursor.rowcount == 0:
- conn.close()
- raise HTTPException(status_code=404, detail="گزارش یافت نشد.")
-
- conn.commit()
- new_upvotes = cursor.execute("SELECT upvotes FROM issues WHERE id = ?", (issue_id,)).fetchone()[0]
- conn.close()
- return {"id": issue_id, "upvotes": new_upvotes}
-
-@app.post("/api/issues/{issue_id}/comment")
-def add_comment(issue_id: int, payload: CommentCreate):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- check = cursor.execute("SELECT id FROM issues WHERE id = ?", (issue_id,)).fetchone()
- if not check:
- conn.close()
- raise HTTPException(status_code=404, detail="گزارش یافت نشد.")
-
- now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
- cursor.execute("""
- INSERT INTO comments (issue_id, author_name, content, created_at)
- VALUES (?, ?, ?, ?)
- """, (issue_id, payload.author_name or "شهروند محترم", payload.content, now_str))
-
- conn.commit()
- comment_id = cursor.lastrowid
- comment = cursor.execute("SELECT * FROM comments WHERE id = ?", (comment_id,)).fetchone()
- conn.close()
- return dict(comment)
-
-@app.patch("/api/issues/{issue_id}/status")
-def update_issue_status(issue_id: int, payload: StatusUpdate):
- conn = get_db_connection()
- cursor = conn.cursor()
-
- check = cursor.execute("SELECT * FROM issues WHERE id = ?", (issue_id,)).fetchone()
- if not check:
- conn.close()
- raise HTTPException(status_code=404, detail="گزارش یافت نشد.")
-
- now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
- resolved_at = now_str if payload.status == "resolved" else check["resolved_at"]
-
- # Update issue
- cursor.execute("""
- UPDATE issues
- SET status = ?,
- official_response = COALESCE(?, official_response),
- resolved_image_url = COALESCE(?, resolved_image_url),
- resolved_at = ?
- WHERE id = ?
- """, (payload.status, payload.official_response, payload.resolved_image_url, resolved_at, issue_id))
-
- # Add timeline entry if requested or automatically
- status_titles = {
- "pending": "در صف بررسی مجدد",
- "reviewing": "بررسی کارشناسی و ارجاع به معاونت مربوطه",
- "in_progress": "اعزام اکیپ اجرایی و آغاز عملیات میدانی",
- "resolved": "اتمام عملیات و رفع کامل مسئله",
- "rejected": "عدم احراز یا خارج از حیطه اختیارات شهرداری"
- }
-
- tl_title = payload.timeline_title or status_titles.get(payload.status, f"تغییر وضعیت به {payload.status}")
- tl_desc = payload.timeline_desc or payload.official_response or "وضعیت پرونده توسط مدیریت سامانه بهروزرسانی شد."
-
- cursor.execute("""
- INSERT INTO timeline (issue_id, status, title, description, created_at)
- VALUES (?, ?, ?, ?, ?)
- """, (issue_id, payload.status, tl_title, tl_desc, now_str))
-
- conn.commit()
-
- updated = cursor.execute("SELECT * FROM issues WHERE id = ?", (issue_id,)).fetchone()
- res = dict(updated)
- conn.close()
- return res
-
-@app.post("/api/upload")
-async def upload_file(file: UploadFile = File(...)):
- filename = f"{uuid.uuid4().hex[:10]}_{file.filename}"
- filepath = os.path.join(UPLOAD_DIR, filename)
- with open(filepath, "wb") as buffer:
- shutil.copyfileobj(file.file, buffer)
- return {"url": f"/static/uploads/{filename}"}
-
-# Serve frontend static files
-app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
-
-@app.get("/")
-def serve_index():
- from fastapi.responses import FileResponse
- return FileResponse(os.path.join(os.path.dirname(__file__), "static", "index.html"))
diff --git a/router.php b/router.php
index 9f5f395..7fb2672 100644
--- a/router.php
+++ b/router.php
@@ -1,45 +1,62 @@
'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'webp' => 'image/webp',
+ 'gif' => 'image/gif'
+ ];
+ $mime = $mimes[$ext] ?? 'application/octet-stream';
+ header('Content-Type: ' . $mime);
+ readfile($filePath);
+ exit;
+ }
+ http_response_code(404);
+ echo "File not found";
+ exit;
+}
+
+// Static Files
+$staticFile = __DIR__ . '/static' . $uri;
+if (file_exists($staticFile) && is_file($staticFile)) {
+ $ext = strtolower(pathinfo($staticFile, PATHINFO_EXTENSION));
$mimes = [
- 'css' => 'text/css',
- 'js' => 'application/javascript',
- 'json' => 'application/json',
+ 'css' => 'text/css; charset=utf-8',
+ 'js' => 'application/javascript; charset=utf-8',
+ 'json' => 'application/json; charset=utf-8',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
- 'gif' => 'image/gif',
'svg' => 'image/svg+xml',
'ico' => 'image/x-icon',
'woff' => 'font/woff',
'woff2'=> 'font/woff2',
'ttf' => 'font/ttf',
+ 'html' => 'text/html; charset=utf-8'
];
-
- if (isset($mimes[$ext])) {
- header('Content-Type: ' . $mimes[$ext]);
- }
- readfile($filePath);
+ $mime = $mimes[$ext] ?? 'text/plain; charset=utf-8';
+ header('Content-Type: ' . $mime);
+ readfile($staticFile);
exit;
}
-// 3. Serve Frontend Application
-if (file_exists(__DIR__ . '/static/index.html')) {
- header('Content-Type: text/html; charset=utf-8');
- readfile(__DIR__ . '/static/index.html');
- exit;
-}
-
-echo "سامانه مدیریت مسائل شهری شهرنگار فعال است.";
+// Default Single Page Application Entry
+header('Content-Type: text/html; charset=utf-8');
+readfile(__DIR__ . '/static/index.html');
diff --git a/static/index.html b/static/index.html
index 3dacfab..c0825c7 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,763 +1,803 @@
-
-
- شهرنگار | سامانه جامع مدیریت و رسیدگی به مسائل شهری
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ FX Journal Pro • ژورنال تخصصی و تحلیلی فارکس
+
+
+
+
+
+
+
+
+
-
+
-
-
-
- سامانه برخط ۱۳۷
- همراهی شما برای شهری زیباتر، ایمنتر و پاکتر • پاسخگویی ۲۴ ساعته
-
-
- امروز شنبه
- |
- تلفن ارتباط مردمی: ۱۳۷
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- شهرنگار
- نسخه هوشمند
+
+
+
+
+
+
+
+
+
+
+
+ FX Journal Pro
+ مایافایکسبوک
+
+
سامانه جامع ژورنالنویسی و تحلیل پیشرفته معاملات
+
-
سامانه یکپارچه گزارش و نظارت مردمی بر مسائل شهری
-
+
+
+
+
+
+ حساب فعال:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- صدای شما، اقدام سریع شهرداری و بازرسان شهری
-
-
- شهر خود را با مشارکت هوشمند،
- زیباتر و امنتر بسازید
-
-
- خرابی آسفالت، سد معبر، نقص روشنایی پارکها، انباشت پسماند یا مشکلات ترافیکی را در کمتر از ۱ دقیقه گزارش کنید و وضعیت رسیدگی را مرحله به مرحله رصد نمایید.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- کل موارد ثبتشده
-
-
-
-
-
--
-
گزارشهای شهروندی
-
-
-
-
- مسائل رفع شده
-
-
-
-
-
--
-
نرخ تکمیل: --٪
-
-
-
-
- در حال عملیات و اجرا
-
-
-
-
-
--
-
اکیپ مستقر در محل
-
-
-
-
- تایید و همراهی شهروندان
-
-
-
-
-
--
-
رأی و حمایت مردمی
-
-
-
-
-
-
-
-
-
-
- دستهبندیها:
-
- همه موضوعات
-
-
- آسفالت و چالهها
-
-
- روشنایی و تیر برق
-
-
- نظافت و سطل زباله
-
-
- بوستان و درختان
-
-
- چراغ و علائم ترافیکی
-
-
- سد معبر و ساختمانی
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
در حال بارگذاری گزارشهای شهری...
-
-
-
-
-
-
-
موردی با این مشخصات یافت نشد
-
میتوانید فیلترها را تغییر دهید یا خودتان اولین گزارش را ثبت کنید.
-
- ثبت گزارش جدید
-
-
-
-
-
-
-
-
-
-
-
- نقشه زنده و پراکندگی مسائل شهری
-
-
برای مشاهده جزئیات، عکسها و وضعیت هر موضوع، روی نشانگرهای نقشه کلیک کنید.
-
-
-
-
- در حال بررسی/اجرا
- رفع شده
- نیازمند اقدام فوری
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
سامانه پیگیری و استعلام گزارشهای شهری
-
- با وارد کردن کد پیگیری (مانند SHR-78214)، از آخرین وضعیت اقدامات شهرداری، اعزام اکیپ و تصاویر قبل و بعد مطلع شوید.
-