Files

625 lines
29 KiB
PHP

<?php
/**
* Forex Trading Journal REST API
* Clean JSON API for accounts, trades, analytics, chart uploads, MT4/MT5 statement import, and exports.
*/
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/analytics.php';
$pdo = Database::getConnection();
$analyticsEngine = new AnalyticsEngine($pdo);
// Parse request path
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
// Helper JSON response
function sendJson(array $data, int $statusCode = 200): void {
http_response_code($statusCode);
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
// Helper to get raw JSON payload
function getJsonInput(): array {
$raw = file_get_contents('php://input');
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
// Helper Pip Calculator
function calculatePips(string $symbol, string $type, float $openPrice, float $closePrice): float {
$sym = strtoupper(trim($symbol));
$type = strtolower(trim($type));
$diff = ($type === 'buy') ? ($closePrice - $openPrice) : ($openPrice - $closePrice);
if (strpos($sym, 'JPY') !== false) {
return round($diff * 100, 1);
} elseif (strpos($sym, 'XAU') !== false || strpos($sym, 'GOLD') !== false) {
return round($diff * 10, 1); // 10 pips per $1.00 move
} elseif (strpos($sym, 'BTC') !== false || strpos($sym, 'ETH') !== false || strpos($sym, 'US30') !== false || strpos($sym, 'NAS') !== false) {
return round($diff, 1); // 1 point
} else {
return round($diff * 10000, 1); // Standard forex 4/5 digits
}
}
try {
// -------------------------------------------------------------
// 1. ACCOUNTS ENDPOINTS
// -------------------------------------------------------------
if (preg_match('#^/api/accounts/?$#', $uri)) {
if ($method === 'GET') {
$stmt = $pdo->query("
SELECT a.*,
(SELECT COUNT(*) FROM trades t WHERE t.account_id = a.id) as total_trades,
(SELECT COALESCE(SUM(profit + commission + swap), 0) FROM trades t WHERE t.account_id = a.id AND t.status = 'closed') as net_closed_profit,
(SELECT COALESCE(SUM(profit + commission + swap), 0) FROM trades t WHERE t.account_id = a.id AND t.status = 'open') as net_open_profit
FROM accounts a ORDER BY a.is_default DESC, a.id ASC
");
$accounts = $stmt->fetchAll();
sendJson(['success' => true, 'accounts' => $accounts]);
} elseif ($method === 'POST') {
$input = getJsonInput();
$name = trim($input['name'] ?? 'حساب معاملاتی جدید');
$broker = trim($input['broker'] ?? 'IC Markets');
$accNum = trim($input['account_number'] ?? '');
$currency = trim($input['currency'] ?? 'USD');
$initBal = (float)($input['initial_balance'] ?? 10000.0);
$leverage = (float)($input['leverage'] ?? 100.0);
$accType = trim($input['account_type'] ?? 'Real');
$isDefault = !empty($input['is_default']) ? 1 : 0;
if ($isDefault) {
$pdo->exec("UPDATE accounts SET is_default = 0");
}
$stmt = $pdo->prepare("
INSERT INTO accounts (name, broker, account_number, currency, initial_balance, leverage, account_type, is_default)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([$name, $broker, $accNum, $currency, $initBal, $leverage, $accType, $isDefault]);
$newId = (int)$pdo->lastInsertId();
// Optionally generate sample data
if (!empty($input['with_sample_data'])) {
Database::generateSampleTrades($newId, $initBal);
}
sendJson(['success' => true, 'id' => $newId, 'message' => 'حساب معاملاتی با موفقیت ایجاد شد']);
}
}
if (preg_match('#^/api/accounts/(\d+)$#', $uri, $matches)) {
$accId = (int)$matches[1];
if ($method === 'GET') {
$stmt = $pdo->prepare("SELECT * FROM accounts WHERE id = ?");
$stmt->execute([$accId]);
$acc = $stmt->fetch();
if (!$acc) sendJson(['success' => false, 'error' => 'حساب یافت نشد'], 404);
sendJson(['success' => true, 'account' => $acc]);
} elseif ($method === 'PUT') {
$input = getJsonInput();
$stmt = $pdo->prepare("
UPDATE accounts SET
name = COALESCE(?, name),
broker = COALESCE(?, broker),
account_number = COALESCE(?, account_number),
currency = COALESCE(?, currency),
initial_balance = COALESCE(?, initial_balance),
leverage = COALESCE(?, leverage),
account_type = COALESCE(?, account_type),
is_default = COALESCE(?, is_default),
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
");
$stmt->execute([
$input['name'] ?? null,
$input['broker'] ?? null,
$input['account_number'] ?? null,
$input['currency'] ?? null,
isset($input['initial_balance']) ? (float)$input['initial_balance'] : null,
isset($input['leverage']) ? (float)$input['leverage'] : null,
$input['account_type'] ?? null,
isset($input['is_default']) ? (int)$input['is_default'] : null,
$accId
]);
sendJson(['success' => true, 'message' => 'حساب به‌روزرسانی شد']);
} elseif ($method === 'DELETE') {
$stmt = $pdo->prepare("DELETE FROM accounts WHERE id = ?");
$stmt->execute([$accId]);
sendJson(['success' => true, 'message' => 'حساب و معاملات آن حذف شدند']);
}
}
// -------------------------------------------------------------
// 2. ANALYTICS ENDPOINT (Myfxbook Style)
// -------------------------------------------------------------
if (preg_match('#^/api/analytics/?$#', $uri)) {
if ($method === 'GET') {
$accountId = (int)($_GET['account_id'] ?? 0);
if ($accountId <= 0) {
// Get default account
$stmtDef = $pdo->query("SELECT id FROM accounts ORDER BY is_default DESC, id ASC LIMIT 1");
$accountId = (int)($stmtDef->fetchColumn() ?: 1);
}
$from = $_GET['from'] ?? null;
$to = $_GET['to'] ?? null;
$symbol = $_GET['symbol'] ?? null;
$strategy = $_GET['strategy'] ?? null;
$data = $analyticsEngine->getFullAnalytics($accountId, $from, $to, $symbol, $strategy);
sendJson(['success' => true, 'data' => $data]);
}
}
// -------------------------------------------------------------
// 3. TRADES CRUD ENDPOINTS
// -------------------------------------------------------------
if (preg_match('#^/api/trades/?$#', $uri)) {
if ($method === 'GET') {
$accountId = (int)($_GET['account_id'] ?? 0);
if ($accountId <= 0) {
$stmtDef = $pdo->query("SELECT id FROM accounts ORDER BY is_default DESC, id ASC LIMIT 1");
$accountId = (int)($stmtDef->fetchColumn() ?: 1);
}
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = max(5, min(100, (int)($_GET['limit'] ?? 50)));
$offset = ($page - 1) * $limit;
$status = $_GET['status'] ?? 'all';
$symbol = $_GET['symbol'] ?? 'all';
$strategy = $_GET['strategy'] ?? 'all';
$search = trim($_GET['search'] ?? '');
$sortBy = $_GET['sort_by'] ?? 'open_time';
$sortDir = strtolower($_GET['sort_dir'] ?? 'desc') === 'asc' ? 'ASC' : 'DESC';
$allowedSorts = ['open_time', 'close_time', 'profit', 'pips', 'lot_size', 'symbol', 'id'];
if (!in_array($sortBy, $allowedSorts)) $sortBy = 'open_time';
$where = ["account_id = ?"];
$params = [$accountId];
if ($status !== 'all') {
$where[] = "status = ?";
$params[] = $status;
}
if ($symbol !== 'all' && !empty($symbol)) {
$where[] = "symbol = ?";
$params[] = $symbol;
}
if ($strategy !== 'all' && !empty($strategy)) {
$where[] = "strategy = ?";
$params[] = $strategy;
}
if (!empty($search)) {
$where[] = "(symbol LIKE ? OR ticket LIKE ? OR entry_notes LIKE ? OR exit_notes LIKE ? OR tags LIKE ?)";
$sParam = "%$search%";
$params[] = $sParam;
$params[] = $sParam;
$params[] = $sParam;
$params[] = $sParam;
$params[] = $sParam;
}
$whereClause = implode(" AND ", $where);
// Total count
$stmtCount = $pdo->prepare("SELECT COUNT(*) FROM trades WHERE $whereClause");
$stmtCount->execute($params);
$totalCount = (int)$stmtCount->fetchColumn();
// Fetch records
$sql = "SELECT * FROM trades WHERE $whereClause ORDER BY $sortBy $sortDir LIMIT $limit OFFSET $offset";
$stmtList = $pdo->prepare($sql);
$stmtList->execute($params);
$trades = $stmtList->fetchAll();
sendJson([
'success' => true,
'trades' => $trades,
'pagination' => [
'total' => $totalCount,
'page' => $page,
'limit' => $limit,
'total_pages' => ceil($totalCount / $limit)
]
]);
} elseif ($method === 'POST') {
$input = getJsonInput();
$accountId = (int)($input['account_id'] ?? 1);
$symbol = strtoupper(trim($input['symbol'] ?? 'EURUSD'));
$tradeType = strtolower(trim($input['trade_type'] ?? 'buy'));
$lotSize = (float)($input['lot_size'] ?? 0.1);
$openPrice = (float)($input['open_price'] ?? 0.0);
$closePrice = isset($input['close_price']) && $input['close_price'] !== '' ? (float)$input['close_price'] : null;
$stopLoss = isset($input['stop_loss']) && $input['stop_loss'] !== '' ? (float)$input['stop_loss'] : null;
$takeProfit = isset($input['take_profit']) && $input['take_profit'] !== '' ? (float)$input['take_profit'] : null;
$openTime = !empty($input['open_time']) ? date('Y-m-d H:i:s', strtotime($input['open_time'])) : date('Y-m-d H:i:s');
$closeTime = !empty($input['close_time']) ? date('Y-m-d H:i:s', strtotime($input['close_time'])) : null;
$status = trim($input['status'] ?? ($closePrice !== null ? 'closed' : 'open'));
$strategy = trim($input['strategy'] ?? 'Price Action');
$timeframe = trim($input['timeframe'] ?? 'M15');
$session = trim($input['session'] ?? 'London');
$emotion = trim($input['emotion'] ?? 'Disciplined');
$comm = (float)($input['commission'] ?? 0.0);
$swap = (float)($input['swap'] ?? 0.0);
$profit = isset($input['profit']) ? (float)$input['profit'] : 0.0;
$pips = isset($input['pips']) ? (float)$input['pips'] : 0.0;
$rr = isset($input['risk_reward']) ? (float)$input['risk_reward'] : null;
// Auto calculate pips if not provided and closed
if ($closePrice !== null && empty($input['pips'])) {
$pips = calculatePips($symbol, $tradeType, $openPrice, $closePrice);
}
// Auto calculate profit if not provided and closed
if ($closePrice !== null && !isset($input['profit'])) {
// Rough standard lot pip value estimation ($10 per lot per pip for EURUSD)
$pipVal = 10.0 * $lotSize;
$profit = round($pips * $pipVal, 2);
}
$stmt = $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, timeframe, session, risk_reward, emotion,
entry_notes, exit_notes, lessons, screenshot_entry, screenshot_exit, tags
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$accountId,
trim($input['ticket'] ?? ''),
$symbol,
$tradeType,
$lotSize,
$openPrice,
$closePrice,
$stopLoss,
$takeProfit,
$openTime,
$closeTime,
$pips,
$profit,
$comm,
$swap,
$status,
$strategy,
$timeframe,
$session,
$rr,
$emotion,
trim($input['entry_notes'] ?? ''),
trim($input['exit_notes'] ?? ''),
trim($input['lessons'] ?? ''),
trim($input['screenshot_entry'] ?? ''),
trim($input['screenshot_exit'] ?? ''),
trim($input['tags'] ?? '')
]);
$tradeId = (int)$pdo->lastInsertId();
sendJson(['success' => true, 'id' => $tradeId, 'message' => 'معامله با موفقیت ثبت شد']);
}
}
if (preg_match('#^/api/trades/(\d+)$#', $uri, $matches)) {
$tradeId = (int)$matches[1];
if ($method === 'GET') {
$stmt = $pdo->prepare("SELECT * FROM trades WHERE id = ?");
$stmt->execute([$tradeId]);
$trade = $stmt->fetch();
if (!$trade) sendJson(['success' => false, 'error' => 'معامله یافت نشد'], 404);
sendJson(['success' => true, 'trade' => $trade]);
} elseif ($method === 'PUT') {
$input = getJsonInput();
$stmtGet = $pdo->prepare("SELECT * FROM trades WHERE id = ?");
$stmtGet->execute([$tradeId]);
$curr = $stmtGet->fetch();
if (!$curr) sendJson(['success' => false, 'error' => 'معامله یافت نشد'], 404);
$symbol = strtoupper(trim($input['symbol'] ?? $curr['symbol']));
$tradeType = strtolower(trim($input['trade_type'] ?? $curr['trade_type']));
$openPrice = isset($input['open_price']) ? (float)$input['open_price'] : (float)$curr['open_price'];
$closePrice = array_key_exists('close_price', $input) ? ($input['close_price'] !== null && $input['close_price'] !== '' ? (float)$input['close_price'] : null) : $curr['close_price'];
$pips = isset($input['pips']) ? (float)$input['pips'] : $curr['pips'];
$profit = isset($input['profit']) ? (float)$input['profit'] : $curr['profit'];
if ($closePrice !== null && (!isset($input['pips']) || $input['pips'] === '')) {
$pips = calculatePips($symbol, $tradeType, $openPrice, (float)$closePrice);
}
$status = $input['status'] ?? ($closePrice !== null ? 'closed' : $curr['status']);
$closeTime = array_key_exists('close_time', $input) ? (!empty($input['close_time']) ? date('Y-m-d H:i:s', strtotime($input['close_time'])) : null) : $curr['close_time'];
$stmt = $pdo->prepare("
UPDATE trades SET
ticket = COALESCE(?, ticket),
symbol = ?,
trade_type = ?,
lot_size = COALESCE(?, lot_size),
open_price = ?,
close_price = ?,
stop_loss = ?,
take_profit = ?,
open_time = COALESCE(?, open_time),
close_time = ?,
pips = ?,
profit = ?,
commission = COALESCE(?, commission),
swap = COALESCE(?, swap),
status = ?,
strategy = COALESCE(?, strategy),
timeframe = COALESCE(?, timeframe),
session = COALESCE(?, session),
risk_reward = ?,
emotion = COALESCE(?, emotion),
entry_notes = COALESCE(?, entry_notes),
exit_notes = COALESCE(?, exit_notes),
lessons = COALESCE(?, lessons),
screenshot_entry = COALESCE(?, screenshot_entry),
screenshot_exit = COALESCE(?, screenshot_exit),
tags = COALESCE(?, tags),
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
");
$stmt->execute([
$input['ticket'] ?? null,
$symbol,
$tradeType,
isset($input['lot_size']) ? (float)$input['lot_size'] : null,
$openPrice,
$closePrice,
isset($input['stop_loss']) && $input['stop_loss'] !== '' ? (float)$input['stop_loss'] : null,
isset($input['take_profit']) && $input['take_profit'] !== '' ? (float)$input['take_profit'] : null,
!empty($input['open_time']) ? date('Y-m-d H:i:s', strtotime($input['open_time'])) : null,
$closeTime,
$pips,
$profit,
isset($input['commission']) ? (float)$input['commission'] : null,
isset($input['swap']) ? (float)$input['swap'] : null,
$status,
$input['strategy'] ?? null,
$input['timeframe'] ?? null,
$input['session'] ?? null,
isset($input['risk_reward']) ? (float)$input['risk_reward'] : null,
$input['emotion'] ?? null,
$input['entry_notes'] ?? null,
$input['exit_notes'] ?? null,
$input['lessons'] ?? null,
$input['screenshot_entry'] ?? null,
$input['screenshot_exit'] ?? null,
$input['tags'] ?? null,
$tradeId
]);
sendJson(['success' => true, 'message' => 'معامله با موفقیت ویرایش شد']);
} elseif ($method === 'DELETE') {
$stmt = $pdo->prepare("DELETE FROM trades WHERE id = ?");
$stmt->execute([$tradeId]);
sendJson(['success' => true, 'message' => 'معامله حذف شد']);
}
}
// -------------------------------------------------------------
// 4. MT4 / MT5 STATEMENT IMPORTER (CSV & HTML)
// -------------------------------------------------------------
if (preg_match('#^/api/trades/import/?$#', $uri) && $method === 'POST') {
$accountId = (int)($_POST['account_id'] ?? 1);
if (!isset($_FILES['statement']) || $_FILES['statement']['error'] !== UPLOAD_ERR_OK) {
sendJson(['success' => false, 'error' => 'فایل گزارش انتخاب نشده یا آپلود ناموفق بود'], 400);
}
$fileTmp = $_FILES['statement']['tmp_name'];
$fileName = $_FILES['statement']['name'];
$content = file_get_contents($fileTmp);
$importedCount = 0;
// Check if file is CSV
if (str_ends_with(strtolower($fileName), '.csv') || strpos($content, ',') !== false && strpos($content, '<html') === false) {
$lines = explode("\n", $content);
$header = [];
foreach ($lines as $lineNum => $line) {
$line = trim($line);
if (empty($line)) continue;
$row = str_getcsv($line);
if ($lineNum === 0 || empty($header)) {
$header = array_map('strtolower', array_map('trim', $row));
continue;
}
if (count($row) < 5) continue;
$rowAssoc = [];
foreach ($header as $idx => $colName) {
$rowAssoc[$colName] = $row[$idx] ?? '';
}
$ticket = $rowAssoc['ticket'] ?? $rowAssoc['order'] ?? (string)rand(1000000, 9999999);
$symbol = strtoupper($rowAssoc['symbol'] ?? $rowAssoc['item'] ?? 'EURUSD');
$type = strtolower($rowAssoc['type'] ?? $rowAssoc['cmd'] ?? 'buy');
if (!in_array($type, ['buy', 'sell'])) continue;
$size = (float)($rowAssoc['size'] ?? $rowAssoc['volume'] ?? $rowAssoc['lots'] ?? 0.1);
$openPrice = (float)($rowAssoc['open price'] ?? $rowAssoc['price'] ?? $rowAssoc['open_price'] ?? 0.0);
$closePrice = (float)($rowAssoc['close price'] ?? $rowAssoc['close_price'] ?? 0.0);
$profit = (float)($rowAssoc['profit'] ?? $rowAssoc['p/l'] ?? 0.0);
$comm = (float)($rowAssoc['commission'] ?? $rowAssoc['taxes'] ?? 0.0);
$swap = (float)($rowAssoc['swap'] ?? 0.0);
$openTime = $rowAssoc['open time'] ?? $rowAssoc['time'] ?? date('Y-m-d H:i:s');
$closeTime = $rowAssoc['close time'] ?? $rowAssoc['time'] ?? date('Y-m-d H:i:s');
$pips = calculatePips($symbol, $type, $openPrice, $closePrice);
$stmtIns = $pdo->prepare("
INSERT INTO trades (
account_id, ticket, symbol, trade_type, lot_size,
open_price, close_price, open_time, close_time, pips, profit, commission, swap, status, strategy, emotion
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'closed', 'Imported MT4/MT5', 'Disciplined')
");
$stmtIns->execute([$accountId, $ticket, $symbol, $type, $size, $openPrice, $closePrice, $openTime, $closeTime, $pips, $profit, $comm, $swap]);
$importedCount++;
}
} else {
// HTML Statement Parser (MT4 / MT5 Standard HTML Reports)
preg_match_all('#<tr[^>]*>(.*?)</tr>#is', $content, $trMatches);
foreach ($trMatches[1] as $trHtml) {
preg_match_all('#<td[^>]*>(.*?)</td>#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);
}