feat: Implement comprehensive Forex Trading Journal platform with Myfxbook analytics
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
// api.php - RESTful API handlers for ShahrNegar
|
||||
/**
|
||||
* Forex Trading Journal REST API
|
||||
* Clean JSON API for accounts, trades, analytics, chart uploads, MT4/MT5 statement import, and exports.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
// Set response headers
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
|
||||
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') {
|
||||
@@ -14,321 +14,611 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
initDB();
|
||||
$pdo = getDB();
|
||||
require_once __DIR__ . '/db.php';
|
||||
require_once __DIR__ . '/analytics.php';
|
||||
|
||||
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$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 jsonResponse($data, int $statusCode = 200) {
|
||||
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 {
|
||||
$input = file_get_contents('php://input');
|
||||
return json_decode($input, true) ?? [];
|
||||
$raw = file_get_contents('php://input');
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
// 1. GET /api/stats
|
||||
if ($requestUri === '/api/stats' && $method === 'GET') {
|
||||
$total = (int)$pdo->query("SELECT COUNT(*) FROM issues")->fetchColumn();
|
||||
$pending = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'pending'")->fetchColumn();
|
||||
$reviewing = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'reviewing'")->fetchColumn();
|
||||
$inProgress = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'in_progress'")->fetchColumn();
|
||||
$resolved = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'resolved'")->fetchColumn();
|
||||
$upvotes = (int)$pdo->query("SELECT COALESCE(SUM(upvotes), 0) FROM issues")->fetchColumn();
|
||||
// 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);
|
||||
|
||||
$catStmt = $pdo->query("SELECT category, COUNT(*) as count FROM issues GROUP BY category ORDER BY count DESC");
|
||||
$categories = $catStmt->fetchAll();
|
||||
|
||||
$distStmt = $pdo->query("SELECT district, COUNT(*) as count FROM issues GROUP BY district ORDER BY district ASC");
|
||||
$districts = $distStmt->fetchAll();
|
||||
|
||||
$prioStmt = $pdo->query("SELECT priority, COUNT(*) as count FROM issues GROUP BY priority");
|
||||
$priorities = [];
|
||||
foreach ($prioStmt->fetchAll() as $row) {
|
||||
$priorities[$row['priority']] = (int)$row['count'];
|
||||
}
|
||||
|
||||
$resolutionRate = $total > 0 ? round(($resolved / $total) * 100, 1) : 0;
|
||||
|
||||
jsonResponse([
|
||||
'total' => $total,
|
||||
'pending' => $pending,
|
||||
'reviewing' => $reviewing,
|
||||
'in_progress' => $inProgress,
|
||||
'resolved' => $resolved,
|
||||
'total_upvotes' => $upvotes,
|
||||
'categories' => $categories,
|
||||
'districts' => $districts,
|
||||
'priorities' => $priorities,
|
||||
'resolution_rate' => $resolutionRate
|
||||
]);
|
||||
}
|
||||
|
||||
// 2. GET /api/issues
|
||||
if ($requestUri === '/api/issues' && $method === 'GET') {
|
||||
$category = $_GET['category'] ?? null;
|
||||
$status = $_GET['status'] ?? null;
|
||||
$district = isset($_GET['district']) ? (int)$_GET['district'] : 0;
|
||||
$priority = $_GET['priority'] ?? null;
|
||||
$search = $_GET['search'] ?? null;
|
||||
$sortBy = $_GET['sort_by'] ?? 'newest';
|
||||
|
||||
$sql = "SELECT * FROM issues WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($category && $category !== 'all') {
|
||||
$sql .= " AND category = ?";
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($status && $status !== 'all') {
|
||||
$sql .= " AND status = ?";
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($district > 0) {
|
||||
$sql .= " AND district = ?";
|
||||
$params[] = $district;
|
||||
}
|
||||
|
||||
if ($priority && $priority !== 'all') {
|
||||
$sql .= " AND priority = ?";
|
||||
$params[] = $priority;
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$sql .= " AND (title LIKE ? OR description LIKE ? OR address LIKE ? OR tracking_code LIKE ?)";
|
||||
$searchParam = "%{$search}%";
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
$params[] = $searchParam;
|
||||
}
|
||||
|
||||
if ($sortBy === 'upvotes') {
|
||||
$sql .= " ORDER BY upvotes DESC, id DESC";
|
||||
} elseif ($sortBy === 'oldest') {
|
||||
$sql .= " ORDER BY id ASC";
|
||||
} elseif ($sortBy === 'emergency') {
|
||||
$sql .= " ORDER BY CASE priority WHEN 'emergency' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END, id DESC";
|
||||
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 {
|
||||
$sql .= " ORDER BY id DESC";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$commentCountStmt = $pdo->prepare("SELECT COUNT(*) FROM comments WHERE issue_id = ?");
|
||||
|
||||
$issues = [];
|
||||
foreach ($rows as $row) {
|
||||
$commentCountStmt->execute([$row['id']]);
|
||||
$row['comment_count'] = (int)$commentCountStmt->fetchColumn();
|
||||
$issues[] = $row;
|
||||
}
|
||||
|
||||
jsonResponse($issues);
|
||||
}
|
||||
|
||||
// 3. POST /api/issues (Create new issue)
|
||||
if ($requestUri === '/api/issues' && $method === 'POST') {
|
||||
$data = getJsonInput();
|
||||
|
||||
if (empty($data['title']) || empty($data['category']) || empty($data['address'])) {
|
||||
jsonResponse(['error' => 'اطلاعات ضروری تکمیل نشده است.'], 400);
|
||||
}
|
||||
|
||||
$trackingCode = 'SHR-' . rand(10000, 99999);
|
||||
$now = date('Y-m-d H:i');
|
||||
|
||||
$stmt = $pdo->prepare("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, ?)");
|
||||
|
||||
$stmt->execute([
|
||||
$trackingCode,
|
||||
$data['title'],
|
||||
$data['category'],
|
||||
$data['description'] ?? '',
|
||||
$data['address'],
|
||||
(int)($data['district'] ?? 1),
|
||||
$data['priority'] ?? 'medium',
|
||||
(float)($data['lat'] ?? 35.7538),
|
||||
(float)($data['lng'] ?? 51.4172),
|
||||
$data['image_url'] ?? 'https://images.unsplash.com/photo-1515162816999-a0c47dc192f7?auto=format&fit=crop&w=800&q=80',
|
||||
$data['reporter_name'] ?? 'شهروند مسئول',
|
||||
$data['reporter_phone'] ?? '',
|
||||
$now
|
||||
]);
|
||||
|
||||
$issueId = $pdo->lastInsertId();
|
||||
|
||||
// Timeline initial record
|
||||
$tlStmt = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, 'pending', 'ثبت اولیه گزارش در سامانه', 'گزارش شما با موفقیت در سامانه شهرنگار ثبت گردید و در نوبت بررسی کارشناسی قرار گرفت.', ?)");
|
||||
$tlStmt->execute([$issueId, $now]);
|
||||
|
||||
$fetchStmt = $pdo->prepare("SELECT * FROM issues WHERE id = ?");
|
||||
$fetchStmt->execute([$issueId]);
|
||||
jsonResponse($fetchStmt->fetch(), 201);
|
||||
}
|
||||
|
||||
// 4. GET /api/track/{code}
|
||||
if (preg_match('#^/api/track/([^/]+)$#', $requestUri, $matches) && $method === 'GET') {
|
||||
$code = strtoupper(trim(urldecode($matches[1])));
|
||||
$stmt = $pdo->prepare("SELECT * FROM issues WHERE UPPER(tracking_code) = ?");
|
||||
$stmt->execute([$code]);
|
||||
$issue = $stmt->fetch();
|
||||
|
||||
if (!$issue) {
|
||||
jsonResponse(['error' => "هیچ گزارشی با کد رهگیری «{$code}» یافت نشد."], 404);
|
||||
}
|
||||
|
||||
$tlStmt = $pdo->prepare("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC");
|
||||
$tlStmt->execute([$issue['id']]);
|
||||
$issue['timeline'] = $tlStmt->fetchAll();
|
||||
|
||||
$cmStmt = $pdo->prepare("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC");
|
||||
$cmStmt->execute([$issue['id']]);
|
||||
$issue['comments'] = $cmStmt->fetchAll();
|
||||
|
||||
jsonResponse($issue);
|
||||
}
|
||||
|
||||
// 5. GET /api/issues/{id}
|
||||
if (preg_match('#^/api/issues/(\d+)$#', $requestUri, $matches) && $method === 'GET') {
|
||||
$issueId = (int)$matches[1];
|
||||
$stmt = $pdo->prepare("SELECT * FROM issues WHERE id = ?");
|
||||
$stmt->execute([$issueId]);
|
||||
$issue = $stmt->fetch();
|
||||
|
||||
if (!$issue) {
|
||||
jsonResponse(['error' => 'گزارش مورد نظر یافت نشد.'], 404);
|
||||
}
|
||||
|
||||
$tlStmt = $pdo->prepare("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC");
|
||||
$tlStmt->execute([$issueId]);
|
||||
$issue['timeline'] = $tlStmt->fetchAll();
|
||||
|
||||
$cmStmt = $pdo->prepare("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC");
|
||||
$cmStmt->execute([$issueId]);
|
||||
$issue['comments'] = $cmStmt->fetchAll();
|
||||
|
||||
jsonResponse($issue);
|
||||
}
|
||||
|
||||
// 6. POST /api/issues/{id}/upvote
|
||||
if (preg_match('#^/api/issues/(\d+)/upvote$#', $requestUri, $matches) && $method === 'POST') {
|
||||
$issueId = (int)$matches[1];
|
||||
$stmt = $pdo->prepare("UPDATE issues SET upvotes = upvotes + 1 WHERE id = ?");
|
||||
$stmt->execute([$issueId]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
jsonResponse(['error' => 'گزارش یافت نشد.'], 404);
|
||||
}
|
||||
|
||||
$fetch = $pdo->prepare("SELECT upvotes FROM issues WHERE id = ?");
|
||||
$fetch->execute([$issueId]);
|
||||
$newUpvotes = (int)$fetch->fetchColumn();
|
||||
|
||||
jsonResponse(['id' => $issueId, 'upvotes' => $newUpvotes]);
|
||||
}
|
||||
|
||||
// 7. POST /api/issues/{id}/comment
|
||||
if (preg_match('#^/api/issues/(\d+)/comment$#', $requestUri, $matches) && $method === 'POST') {
|
||||
$issueId = (int)$matches[1];
|
||||
$data = getJsonInput();
|
||||
|
||||
if (empty($data['content'])) {
|
||||
jsonResponse(['error' => 'متن نظر نمیتواند خالی باشد.'], 400);
|
||||
}
|
||||
|
||||
$check = $pdo->prepare("SELECT id FROM issues WHERE id = ?");
|
||||
$check->execute([$issueId]);
|
||||
if (!$check->fetch()) {
|
||||
jsonResponse(['error' => 'گزارش یافت نشد.'], 404);
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i');
|
||||
$author = !empty($data['author_name']) ? trim($data['author_name']) : 'شهروند محترم';
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO comments (issue_id, author_name, content, created_at) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$issueId, $author, trim($data['content']), $now]);
|
||||
$commentId = $pdo->lastInsertId();
|
||||
|
||||
$cm = $pdo->prepare("SELECT * FROM comments WHERE id = ?");
|
||||
$cm->execute([$commentId]);
|
||||
jsonResponse($cm->fetch(), 201);
|
||||
}
|
||||
|
||||
// 8. PATCH / POST /api/issues/{id}/status (Admin Status Update)
|
||||
if (preg_match('#^/api/issues/(\d+)/status$#', $requestUri, $matches) && in_array($method, ['PATCH', 'POST'])) {
|
||||
$issueId = (int)$matches[1];
|
||||
$data = getJsonInput();
|
||||
|
||||
$check = $pdo->prepare("SELECT * FROM issues WHERE id = ?");
|
||||
$check->execute([$issueId]);
|
||||
$current = $check->fetch();
|
||||
if (!$current) {
|
||||
jsonResponse(['error' => 'گزارش یافت نشد.'], 404);
|
||||
}
|
||||
|
||||
$newStatus = $data['status'] ?? $current['status'];
|
||||
$officialResponse = $data['official_response'] ?? $current['official_response'];
|
||||
$resolvedImageUrl = $data['resolved_image_url'] ?? $current['resolved_image_url'];
|
||||
|
||||
$now = date('Y-m-d H:i');
|
||||
$resolvedAt = ($newStatus === 'resolved') ? $now : $current['resolved_at'];
|
||||
|
||||
$upd = $pdo->prepare("UPDATE issues SET status = ?, official_response = ?, resolved_image_url = ?, resolved_at = ? WHERE id = ?");
|
||||
$upd->execute([$newStatus, $officialResponse, $resolvedImageUrl, $resolvedAt, $issueId]);
|
||||
|
||||
$statusTitles = [
|
||||
'pending' => 'در صف بررسی مجدد',
|
||||
'reviewing' => 'بررسی کارشناسی و ارجاع به معاونت مربوطه',
|
||||
'in_progress' => 'اعزام اکیپ اجرایی و آغاز عملیات میدانی',
|
||||
'resolved' => 'اتمام عملیات و رفع کامل مسئله',
|
||||
'rejected' => 'عدم احراز یا خارج از حیطه اختیارات شهرداری'
|
||||
];
|
||||
|
||||
$tlTitle = !empty($data['timeline_title']) ? $data['timeline_title'] : ($statusTitles[$newStatus] ?? "تغییر وضعیت به {$newStatus}");
|
||||
$tlDesc = !empty($data['timeline_desc']) ? $data['timeline_desc'] : ($officialResponse ?: 'وضعیت پرونده توسط مدیریت سامانه بهروزرسانی شد.');
|
||||
|
||||
$tlStmt = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, ?, ?, ?, ?)");
|
||||
$tlStmt->execute([$issueId, $newStatus, $tlTitle, $tlDesc, $now]);
|
||||
|
||||
$updated = $pdo->prepare("SELECT * FROM issues WHERE id = ?");
|
||||
$updated->execute([$issueId]);
|
||||
jsonResponse($updated->fetch());
|
||||
}
|
||||
|
||||
// 9. POST /api/upload
|
||||
if ($requestUri === '/api/upload' && $method === 'POST') {
|
||||
if (!isset($_FILES['file'])) {
|
||||
jsonResponse(['error' => 'فایلی ارسال نشده است.'], 400);
|
||||
}
|
||||
|
||||
$uploadDir = __DIR__ . '/static/uploads/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
|
||||
$filename = uniqid('img_', true) . '.' . $ext;
|
||||
$target = $uploadDir . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
|
||||
jsonResponse(['url' => '/static/uploads/' . $filename]);
|
||||
} else {
|
||||
jsonResponse(['error' => 'خطا در ذخیرهسازی فایل.'], 500);
|
||||
return round($diff * 10000, 1); // Standard forex 4/5 digits
|
||||
}
|
||||
}
|
||||
|
||||
// Default 404 for API
|
||||
jsonResponse(['error' => 'مسیر مورد نظر در API یافت نشد.'], 404);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user