/home/devscfvi/AblePro/api.php
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
require_once 'config.php';
require_once 'auth_check.php'; // session start + requireAuth() + ensureAuthTables()
$pdo = getDBConnection();
// ── Bootstrap auth tables on first load ────────────────────
ensureAuthTables($pdo);
// ── Resolve current user (returns 0 for public actions) ────
$currentUserId = requireAuth();
// getInput() with body caching so it can be read twice
function getInput(): array {
static $cached = null;
if ($cached === null) {
// Use the body cached by auth_check.php (stream already consumed there)
$raw = $GLOBALS['_request_body'] ?? file_get_contents('php://input');
$cached = json_decode($raw, true) ?? [];
}
return $cached;
}
function sendJSON($data): void {
echo json_encode($data);
exit;
}
// ============================================
// GET REQUESTS
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$action = $_GET['action'] ?? '';
switch($action) {
case 'getTrades':
$mode = $_GET['mode'] ?? 'crypto';
$stmt = $pdo->prepare("SELECT * FROM crypto_trades WHERE trade_mode = ? AND (user_id = ? OR user_id IS NULL) ORDER BY created_at DESC");
$stmt->execute([$mode, $currentUserId ?: 0]);
$trades = $stmt->fetchAll();
// Decode screenshots JSON array for each trade
foreach ($trades as &$t) {
if (!empty($t['screenshots'])) {
$decoded = json_decode($t['screenshots'], true);
$t['screenshots'] = is_array($decoded) ? $decoded : [];
} else {
$t['screenshots'] = [];
}
}
unset($t);
sendJSON(['success' => true, 'trades' => $trades]);
case 'getStats':
$mode = $_GET['mode'] ?? 'crypto';
$stmt = $pdo->prepare("
SELECT
COUNT(*) as total_trades,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
SUM(CASE WHEN status = 'loss' THEN 1 ELSE 0 END) as losses,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_trades,
SUM(CASE WHEN status = 'win' THEN profit_amount WHEN status = 'loss' THEN -loss_amount ELSE 0 END) as total_pnl,
AVG(reward_risk_ratio) as avg_rr,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN status IN ('win', 'loss') THEN 1 ELSE 0 END), 0), 2) as win_rate,
MAX(CASE WHEN status = 'win' THEN profit_amount ELSE 0 END) as best_win,
MAX(CASE WHEN status = 'loss' THEN loss_amount ELSE 0 END) as worst_loss
FROM crypto_trades
WHERE trade_mode = ? AND (user_id = ? OR user_id IS NULL)
");
$stmt->execute([$mode, $currentUserId ?: 0]);
$stats = $stmt->fetch();
sendJSON(['success' => true, 'stats' => $stats]);
case 'getAdvancedAnalytics':
$mode = $_GET['mode'] ?? 'crypto';
// Performance by category (mode-filtered inline query)
$stmt = $pdo->prepare("
SELECT
trade_category as category,
COUNT(*) as total,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
SUM(CASE WHEN status = 'loss' THEN 1 ELSE 0 END) as losses,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 /
NULLIF(SUM(CASE WHEN status IN ('win', 'loss') THEN 1 ELSE 0 END), 0), 2) as win_rate,
SUM(CASE WHEN status = 'win' THEN profit_amount WHEN status = 'loss' THEN -loss_amount ELSE 0 END) as net_pnl
FROM crypto_trades
WHERE trade_category IS NOT NULL AND trade_mode = ?
GROUP BY trade_category
");
$stmt->execute([$mode]);
$byCategory = $stmt->fetchAll();
// Performance by coin/asset (mode-filtered)
$stmt = $pdo->prepare("
SELECT
coin,
COUNT(*) as total,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
SUM(CASE WHEN status = 'loss' THEN 1 ELSE 0 END) as losses,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 /
NULLIF(SUM(CASE WHEN status IN ('win', 'loss') THEN 1 ELSE 0 END), 0), 2) as win_rate,
SUM(CASE WHEN status = 'win' THEN profit_amount WHEN status = 'loss' THEN -loss_amount ELSE 0 END) as net_pnl
FROM crypto_trades
WHERE trade_mode = ?
GROUP BY coin
ORDER BY net_pnl DESC
");
$stmt->execute([$mode]);
$byCoin = $stmt->fetchAll();
// Emotion analysis
$stmt = $pdo->prepare("
SELECT
emotion,
COUNT(*) as count,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as win_rate
FROM crypto_trades
WHERE emotion IS NOT NULL AND status IN ('win', 'loss') AND trade_mode = ?
GROUP BY emotion
");
$stmt->execute([$mode]);
$byEmotion = $stmt->fetchAll();
// Time-based analysis (by hour)
$stmt = $pdo->prepare("
SELECT
HOUR(created_at) as hour,
COUNT(*) as trades,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 /
NULLIF(SUM(CASE WHEN status IN ('win', 'loss') THEN 1 ELSE 0 END), 0), 2) as win_rate
FROM crypto_trades
WHERE status IN ('win', 'loss') AND trade_mode = ?
GROUP BY HOUR(created_at)
ORDER BY hour
");
$stmt->execute([$mode]);
$byHour = $stmt->fetchAll();
// Consecutive wins/losses (streaks)
$stmt = $pdo->prepare("
SELECT status, created_at
FROM crypto_trades
WHERE status IN ('win', 'loss') AND trade_mode = ?
ORDER BY created_at DESC
LIMIT 50
");
$stmt->execute([$mode]);
$recentTrades = $stmt->fetchAll();
$currentStreak = 0;
$streakType = null;
$maxWinStreak = 0;
$maxLossStreak = 0;
$currentWinStreak = 0;
$currentLossStreak = 0;
foreach ($recentTrades as $trade) {
if ($streakType === null) {
$streakType = $trade['status'];
$currentStreak = 1;
} elseif ($trade['status'] === $streakType) {
$currentStreak++;
} else {
if ($streakType === 'win') {
$maxWinStreak = max($maxWinStreak, $currentStreak);
} else {
$maxLossStreak = max($maxLossStreak, $currentStreak);
}
$streakType = $trade['status'];
$currentStreak = 1;
}
}
if ($streakType === 'win') {
$maxWinStreak = max($maxWinStreak, $currentStreak);
$currentWinStreak = $currentStreak;
} else {
$maxLossStreak = max($maxLossStreak, $currentStreak);
$currentLossStreak = $currentStreak;
}
sendJSON([
'success' => true,
'analytics' => [
'byCategory' => $byCategory,
'byCoin' => $byCoin,
'byEmotion' => $byEmotion,
'byHour' => $byHour,
'streaks' => [
'current_win_streak' => $currentWinStreak,
'current_loss_streak' => $currentLossStreak,
'max_win_streak' => $maxWinStreak,
'max_loss_streak' => $maxLossStreak
]
]
]);
case 'getMistakes':
$stmt = $pdo->query("SELECT * FROM v_mistake_analysis");
$mistakes = $stmt->fetchAll();
$stmt = $pdo->query("SELECT * FROM trade_mistakes WHERE is_active = 1 ORDER BY category, mistake_name");
$mistakeTypes = $stmt->fetchAll();
sendJSON(['success' => true, 'mistakes' => $mistakes, 'mistakeTypes' => $mistakeTypes]);
case 'getGoals':
$stmt = $pdo->prepare("SELECT * FROM trading_goals WHERE period_end >= CURDATE() AND (user_id = ? OR user_id IS NULL) ORDER BY period_start DESC");
$stmt->execute([$currentUserId ?: 0]);
$goals = $stmt->fetchAll();
sendJSON(['success' => true, 'goals' => $goals]);
case 'getGoalProgress':
$goalId = $_GET['goalId'] ?? null;
if (!$goalId) {
sendJSON(['success' => false, 'error' => 'Goal ID required']);
}
$stmt = $pdo->prepare("SELECT * FROM trading_goals WHERE id = ?");
$stmt->execute([$goalId]);
$goal = $stmt->fetch();
if (!$goal) {
sendJSON(['success' => false, 'error' => 'Goal not found']);
}
// Get trades in this period
$stmt = $pdo->prepare("
SELECT
COUNT(*) as total_trades,
SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) as wins,
SUM(CASE WHEN status = 'loss' THEN 1 ELSE 0 END) as losses,
SUM(CASE WHEN status = 'win' THEN profit_amount WHEN status = 'loss' THEN -loss_amount ELSE 0 END) as total_pnl,
ROUND(SUM(CASE WHEN status = 'win' THEN 1 ELSE 0 END) * 100.0 /
NULLIF(SUM(CASE WHEN status IN ('win', 'loss') THEN 1 ELSE 0 END), 0), 2) as win_rate,
AVG(reward_risk_ratio) as avg_rr
FROM crypto_trades
WHERE DATE(created_at) BETWEEN ? AND ?
");
$stmt->execute([$goal['period_start'], $goal['period_end']]);
$progress = $stmt->fetch();
sendJSON(['success' => true, 'goal' => $goal, 'progress' => $progress]);
case 'getJournalEntries':
$limit = $_GET['limit'] ?? 30;
$stmt = $pdo->prepare("
SELECT * FROM trading_journal_entries
ORDER BY entry_date DESC
LIMIT ?
");
$stmt->execute([$limit]);
$entries = $stmt->fetchAll();
sendJSON(['success' => true, 'entries' => $entries]);
break;
case 'getBudget':
$stmt = $pdo->prepare("SELECT budget FROM user_budget WHERE (user_id = ? OR user_id IS NULL) ORDER BY id DESC LIMIT 1");
$stmt->execute([$currentUserId ?: 0]);
$result = $stmt->fetch();
sendJSON(['success' => true, 'budget' => $result ? $result['budget'] : null]);
break;
case 'getBudgetTransactions':
$stmt = $pdo->query("
SELECT * FROM budget_transactions
ORDER BY created_at DESC
LIMIT 50
");
$transactions = $stmt->fetchAll();
sendJSON(['success' => true, 'transactions' => $transactions]);
break;
case 'getFavoriteCoins':
$stmt = $pdo->prepare("SELECT coins FROM favorite_coins WHERE (user_id = ? OR user_id IS NULL) ORDER BY id DESC LIMIT 1");
$stmt->execute([$currentUserId ?: 0]);
$result = $stmt->fetch();
$coins = $result ? json_decode($result['coins']) : ['BTC', 'ETH', 'SOL', 'SUI', 'XRP', 'ADA', 'LINK'];
sendJSON(['success' => true, 'coins' => $coins]);
break;
default:
sendJSON(['success' => false, 'error' => 'Invalid action']);
}
}
// ============================================
// POST REQUESTS
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = getInput();
$action = $input['action'] ?? '';
switch($action) {
case 'saveTrade':
$trade = $input['trade'];
$stmt = $pdo->prepare("
INSERT INTO crypto_trades
(coin, position_size, entry_price, stop_loss_percent, take_profit_percent,
quantity, sl_price, tp_price, loss_amount, profit_amount, reward_risk_ratio,
fees, order_type, comment, trade_category, emotion, discipline_score,
mistake_type, lesson_learned, pre_trade_plan, entry_screenshot, trade_mode, screenshots, user_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$screenshotsJson = isset($trade['screenshots']) && is_array($trade['screenshots'])
? json_encode($trade['screenshots'])
: null;
$stmt->execute([
$trade['coin'],
$trade['positionSize'],
$trade['entryPrice'],
$trade['stopLossPercent'],
$trade['takeProfitPercent'],
$trade['quantity'],
$trade['slPrice'],
$trade['tpPrice'],
$trade['lossAmount'],
$trade['profitAmount'],
$trade['rewardRiskRatio'],
$trade['fees'],
$trade['orderType'] ?? 'market',
$trade['comment'] ?? '',
$trade['category'] ?? null,
$trade['emotion'] ?? null,
$trade['disciplineScore'] ?? null,
$trade['mistakeType'] ?? null,
$trade['lessonLearned'] ?? null,
$trade['preTradePlan'] ?? null,
$trade['entryScreenshot'] ?? null,
$trade['tradeMode'] ?? 'crypto',
$screenshotsJson,
$currentUserId ?: null
]);
$tradeId = $pdo->lastInsertId();
// Save tags if provided
if (!empty($trade['tags'])) {
$stmt = $pdo->prepare("INSERT INTO trade_tags (trade_id, tag) VALUES (?, ?)");
foreach ($trade['tags'] as $tag) {
$stmt->execute([$tradeId, $tag]);
}
}
sendJSON(['success' => true, 'tradeId' => $tradeId]);
case 'updateTradeStatus':
$stmt = $pdo->prepare("UPDATE crypto_trades SET status = ?, comment = ? WHERE id = ? AND (user_id = ? OR user_id IS NULL)");
$stmt->execute([$input['status'], $input['comment'] ?? '', $input['id'], $currentUserId ?: 0]);
sendJSON(['success' => true]);
case 'updateTradePrices':
$id = $input['id'];
$slPrice = $input['slPrice'];
$tpPrice = $input['tpPrice'];
// Get the trade to recalculate values
$stmt = $pdo->prepare("SELECT * FROM crypto_trades WHERE id = ?");
$stmt->execute([$id]);
$trade = $stmt->fetch();
if ($trade) {
$entry = floatval($trade['entry_price']);
$position = floatval($trade['position_size']);
$quantity = floatval($trade['quantity']);
// Recalculate percentages
$slPercent = (($entry - floatval($slPrice)) / $entry) * 100;
$tpPercent = ((floatval($tpPrice) - $entry) / $entry) * 100;
// Recalculate loss and profit amounts
$slSellValue = $quantity * floatval($slPrice);
$slSellFee = $slSellValue * 0.001;
$lossAmount = $position - ($slSellValue - $slSellFee);
$tpSellValue = $quantity * floatval($tpPrice);
$tpSellFee = $tpSellValue * 0.001;
$profitAmount = ($tpSellValue - $tpSellFee) - $position;
$rewardRiskRatio = $profitAmount / $lossAmount;
$stmt = $pdo->prepare("
UPDATE crypto_trades
SET sl_price = ?, tp_price = ?,
stop_loss_percent = ?, take_profit_percent = ?,
loss_amount = ?, profit_amount = ?,
reward_risk_ratio = ?
WHERE id = ?
");
$stmt->execute([
$slPrice,
$tpPrice,
round($slPercent, 2),
round($tpPercent, 2),
round($lossAmount, 2),
round($profitAmount, 2),
round($rewardRiskRatio, 2),
$id
]);
}
sendJSON(['success' => true]);
case 'updateTradeReview':
$reviewScreenshotsJson = isset($input['screenshots']) && is_array($input['screenshots'])
? json_encode($input['screenshots'])
: null;
$stmt = $pdo->prepare("
UPDATE crypto_trades
SET post_trade_review = ?, emotion = ?, discipline_score = ?,
mistake_type = ?, lesson_learned = ?, exit_screenshot = ?,
screenshots = COALESCE(?, screenshots)
WHERE id = ?
");
$stmt->execute([
$input['review'] ?? '',
$input['emotion'] ?? null,
$input['disciplineScore'] ?? null,
$input['mistakeType'] ?? null,
$input['lessonLearned'] ?? '',
$input['exitScreenshot'] ?? null,
$reviewScreenshotsJson,
$input['id']
]);
sendJSON(['success' => true]);
break;
case 'closeTradeWithPnL':
$tradeId = $input['id'];
$status = $input['status']; // 'win' or 'loss'
$actualExitPrice = $input['actualExitPrice'];
$actualPnl = $input['actualPnl'];
// 1. Get trade details
$stmt = $pdo->prepare("SELECT coin FROM crypto_trades WHERE id = ?");
$stmt->execute([$tradeId]);
$trade = $stmt->fetch();
if (!$trade) {
sendJSON(['success' => false, 'error' => 'Trade not found']);
break;
}
// 2. Update trade with result
$stmt = $pdo->prepare("
UPDATE crypto_trades
SET status = ?,
actual_exit_price = ?,
actual_pnl = ?
WHERE id = ?
");
$stmt->execute([$status, $actualExitPrice, $actualPnl, $tradeId]);
// 3. Create budget transaction
$description = "Trade P&L: {$trade['coin']} - " . strtoupper($status);
$transType = ($actualPnl >= 0) ? 'deposit' : 'withdrawal';
$transAmount = abs($actualPnl);
$stmt = $pdo->prepare("
INSERT INTO budget_transactions (transaction_type, amount, description)
VALUES (?, ?, ?)
");
$stmt->execute([$transType, $transAmount, $description]);
// 4. Update budget
$budgetStmt = $pdo->prepare("SELECT budget FROM user_budget WHERE (user_id = ? OR user_id IS NULL) ORDER BY id DESC LIMIT 1");
$budgetStmt->execute([$currentUserId ?: 0]);
$budgetResult = $budgetStmt->fetch();
$currentBudget = $budgetResult ? floatval($budgetResult['budget']) : 0;
$newBudget = $currentBudget + floatval($actualPnl);
$pdo->prepare("DELETE FROM user_budget WHERE user_id = ?")->execute([$currentUserId ?: 0]);
$updateStmt = $pdo->prepare("INSERT INTO user_budget (budget, user_id) VALUES (?, ?)");
$updateStmt->execute([$newBudget, $currentUserId ?: null]);
sendJSON(['success' => true, 'newBudget' => $newBudget]);
break;
case 'addTradeScreenshot':
// Append a screenshot URL to the trade's screenshots JSON array
$stmt = $pdo->prepare("SELECT screenshots FROM crypto_trades WHERE id = ?");
$stmt->execute([$input['id']]);
$row = $stmt->fetch();
$existing = ($row && $row['screenshots']) ? json_decode($row['screenshots'], true) : [];
if (!is_array($existing)) $existing = [];
$existing[] = $input['url'];
$stmt = $pdo->prepare("UPDATE crypto_trades SET screenshots = ? WHERE id = ?");
$stmt->execute([json_encode($existing), $input['id']]);
sendJSON(['success' => true, 'screenshots' => $existing]);
case 'removeTradeScreenshot':
// Remove a screenshot URL from the trade's screenshots JSON array
$stmt = $pdo->prepare("SELECT screenshots FROM crypto_trades WHERE id = ?");
$stmt->execute([$input['id']]);
$row = $stmt->fetch();
$existing = ($row && $row['screenshots']) ? json_decode($row['screenshots'], true) : [];
if (!is_array($existing)) $existing = [];
$existing = array_values(array_filter($existing, fn($u) => $u !== $input['url']));
$stmt = $pdo->prepare("UPDATE crypto_trades SET screenshots = ? WHERE id = ?");
$stmt->execute([json_encode($existing), $input['id']]);
sendJSON(['success' => true, 'screenshots' => $existing]);
case 'deleteTrade':
$stmt = $pdo->prepare("DELETE FROM crypto_trades WHERE id = ? AND (user_id = ? OR user_id IS NULL)");
$stmt->execute([$input['id'], $currentUserId ?: 0]);
sendJSON(['success' => true]);
case 'saveGoal':
$stmt = $pdo->prepare("
INSERT INTO trading_goals
(goal_type, target_profit, target_win_rate, max_daily_loss,
min_risk_reward, period_start, period_end, notes, user_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$input['goalType'],
$input['targetProfit'] ?? null,
$input['targetWinRate'] ?? null,
$input['maxDailyLoss'] ?? null,
$input['minRiskReward'] ?? null,
$input['periodStart'],
$input['periodEnd'],
$input['notes'] ?? '',
$currentUserId ?: null
]);
sendJSON(['success' => true, 'goalId' => $pdo->lastInsertId()]);
case 'deleteGoal':
$stmt = $pdo->prepare("DELETE FROM trading_goals WHERE id = ?");
$stmt->execute([$input['id']]);
sendJSON(['success' => true]);
case 'saveJournalEntry':
$stmt = $pdo->prepare("
INSERT INTO trading_journal_entries
(entry_date, market_condition, overall_emotion, what_went_well,
what_went_wrong, lessons_learned, tomorrow_focus, energy_level, user_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
market_condition = VALUES(market_condition),
overall_emotion = VALUES(overall_emotion),
what_went_well = VALUES(what_went_well),
what_went_wrong = VALUES(what_went_wrong),
lessons_learned = VALUES(lessons_learned),
tomorrow_focus = VALUES(tomorrow_focus),
energy_level = VALUES(energy_level)
");
$stmt->execute([
$input['entryDate'],
$input['marketCondition'] ?? null,
$input['overallEmotion'] ?? null,
$input['whatWentWell'] ?? '',
$input['whatWentWrong'] ?? '',
$input['lessonsLearned'] ?? '',
$input['tomorrowFocus'] ?? '',
$input['energyLevel'] ?? null,
$currentUserId ?: null
]);
sendJSON(['success' => true]);
case 'saveBudget':
$pdo->query("DELETE FROM user_budget");
$stmt = $pdo->prepare("INSERT INTO user_budget (budget) VALUES (?)");
$stmt->execute([$input['budget']]);
sendJSON(['success' => true]);
case 'addBudgetTransaction':
// Add transaction
$stmt = $pdo->prepare("
INSERT INTO budget_transactions (transaction_type, amount, description, user_id)
VALUES (?, ?, ?, ?)
");
$stmt->execute([
$input['type'],
$input['amount'],
$input['description'] ?? null,
$currentUserId ?: null
]);
// Update budget
$currentBudget = 0;
$budgetStmt = $pdo->prepare("SELECT budget FROM user_budget WHERE (user_id = ? OR user_id IS NULL) ORDER BY id DESC LIMIT 1");
$budgetStmt->execute([$currentUserId ?: 0]);
$budgetResult = $budgetStmt->fetch();
if ($budgetResult) {
$currentBudget = floatval($budgetResult['budget']);
}
$newBudget = $input['type'] === 'deposit'
? $currentBudget + floatval($input['amount'])
: $currentBudget - floatval($input['amount']);
$pdo->prepare("DELETE FROM user_budget WHERE user_id = ?")->execute([$currentUserId ?: 0]);
$updateStmt = $pdo->prepare("INSERT INTO user_budget (budget, user_id) VALUES (?, ?)");
$updateStmt->execute([$newBudget, $currentUserId ?: null]);
sendJSON(['success' => true, 'newBudget' => $newBudget]);
break;
case 'saveFavoriteCoins':
$coins = json_encode($input['coins']);
$pdo->query("DELETE FROM favorite_coins");
$stmt = $pdo->prepare("INSERT INTO favorite_coins (coins) VALUES (?)");
$stmt->execute([$coins]);
sendJSON(['success' => true]);
break;
case 'bulkImportTrades':
$trades = $input['trades'] ?? [];
$mode = $input['mode'] ?? 'crypto';
$imported = 0;
$errors = [];
$stmt = $pdo->prepare("
INSERT INTO crypto_trades
(coin, position_size, entry_price, stop_loss_percent, take_profit_percent,
quantity, sl_price, tp_price, loss_amount, profit_amount, reward_risk_ratio,
fees, order_type, comment, trade_category, emotion, discipline_score,
mistake_type, lesson_learned, status, trade_mode, trade_date, actual_exit_price)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
foreach ($trades as $i => $t) {
try {
// Validate trade_date format (YYYY-MM-DD); fallback to NULL
$tradeDate = null;
if (!empty($t['tradeDate'])) {
$d = \DateTime::createFromFormat('Y-m-d', $t['tradeDate']);
if ($d && $d->format('Y-m-d') === $t['tradeDate']) {
$tradeDate = $t['tradeDate'];
}
}
$stmt->execute([
$t['coin'] ?? '',
$t['positionSize'] ?? 0,
$t['entryPrice'] ?? 0,
$t['stopLossPercent'] ?? 0,
$t['takeProfitPercent'] ?? 0,
$t['quantity'] ?? 0,
$t['slPrice'] ?? 0,
$t['tpPrice'] ?? 0,
$t['lossAmount'] ?? 0,
$t['profitAmount'] ?? 0,
$t['rewardRiskRatio'] ?? 0,
$t['fees'] ?? 0,
$t['orderType'] ?? 'market',
$t['comment'] ?? '',
$t['category'] ?? null,
$t['emotion'] ?? null,
$t['disciplineScore'] ?? null,
$t['mistakeType'] ?? null,
$t['lessonLearned'] ?? null,
$t['status'] ?? 'pending',
$mode,
$tradeDate,
isset($t['actualExitPrice']) && $t['actualExitPrice'] > 0 ? $t['actualExitPrice'] : null
]);
$imported++;
} catch (\PDOException $e) {
$errors[] = "Row $i: " . $e->getMessage();
}
}
sendJSON(['success' => true, 'imported' => $imported, 'errors' => $errors]);
break;
// ============================================================
// AUTH ACTIONS (inserted into POST switch before 'default')
// ============================================================
case 'checkSession':
$uid = getCurrentUserId();
if ($uid) {
$stmt = $pdo->prepare("SELECT id, first_name, last_name, email, trade_mode, trial_ends_at, subscribed FROM users WHERE id = ?");
$stmt->execute([$uid]);
$user = $stmt->fetch();
sendJSON(['success' => true, 'user' => $user]);
}
sendJSON(['success' => false, 'error' => 'Not logged in']);
case 'login':
$email = trim($input['email'] ?? '');
$password = trim($input['password'] ?? '');
if (!$email || !$password) {
sendJSON(['success' => false, 'error' => 'Email and password are required']);
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? LIMIT 1");
$stmt->execute([strtolower($email)]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password_hash'])) {
sendJSON(['success' => false, 'error' => 'Invalid email or password']);
}
// Regenerate session ID on login (session fixation protection)
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['trade_mode'] = $user['trade_mode'];
sendJSON([
'success' => true,
'user' => [
'id' => $user['id'],
'first_name' => $user['first_name'],
'last_name' => $user['last_name'],
'email' => $user['email'],
'trade_mode' => $user['trade_mode'],
]
]);
case 'register':
$firstName = trim($input['firstName'] ?? '');
$lastName = trim($input['lastName'] ?? '');
$email = strtolower(trim($input['email'] ?? ''));
$password = $input['password'] ?? '';
$tradeMode = in_array($input['tradeMode'] ?? '', ['crypto','stocks'])
? $input['tradeMode'] : 'crypto';
// Validate
if (!$firstName || !$email || !$password) {
sendJSON(['success' => false, 'error' => 'First name, email and password are required']);
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
sendJSON(['success' => false, 'error' => 'Invalid email address']);
}
if (strlen($password) < 8) {
sendJSON(['success' => false, 'error' => 'Password must be at least 8 characters']);
}
// Check duplicate email
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
if ($stmt->fetch()) {
sendJSON(['success' => false, 'error' => 'An account with this email already exists']);
}
// Insert user with 7-day trial
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$trialEnd = (new \DateTime('+7 days'))->format('Y-m-d H:i:s');
$stmt = $pdo->prepare("
INSERT INTO users (first_name, last_name, email, password_hash, trade_mode, trial_ends_at)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([$firstName, $lastName, $email, $hash, $tradeMode, $trialEnd]);
$newUserId = (int)$pdo->lastInsertId();
// Auto-login
session_regenerate_id(true);
$_SESSION['user_id'] = $newUserId;
$_SESSION['user_email'] = $email;
$_SESSION['trade_mode'] = $tradeMode;
sendJSON([
'success' => true,
'user' => [
'id' => $newUserId,
'first_name' => $firstName,
'last_name' => $lastName,
'email' => $email,
'trade_mode' => $tradeMode,
'trial_ends_at'=> $trialEnd,
]
]);
case 'logout':
$_SESSION = [];
session_destroy();
sendJSON(['success' => true]);
case 'requestPasswordReset':
$email = strtolower(trim($input['email'] ?? ''));
if (!$email) sendJSON(['success' => false, 'error' => 'Email is required']);
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
// Always return success to prevent email enumeration
if ($user) {
$token = bin2hex(random_bytes(32));
$tokenHash = hash('sha256', $token);
$expires = (new \DateTime('+1 hour'))->format('Y-m-d H:i:s');
// Invalidate old tokens for this user
$pdo->prepare("UPDATE password_resets SET used = 1 WHERE user_id = ?")
->execute([$user['id']]);
$pdo->prepare("INSERT INTO password_resets (user_id, token_hash, expires_at) VALUES (?, ?, ?)")
->execute([$user['id'], $tokenHash, $expires]);
// TODO: send email with reset link:
// $resetUrl = "https://yourdomain.com/pages/reset-password-v1.html?token={$token}";
// mail($email, 'Reset your TradeJournal password', "Click here: {$resetUrl}");
//
// For now, return the token in dev mode so you can test it:
sendJSON(['success' => true, 'dev_token' => $token]);
}
sendJSON(['success' => true]); // don't reveal whether email exists
case 'resetPassword':
$token = $input['token'] ?? '';
$password = $input['password'] ?? '';
if (!$token || strlen($password) < 8) {
sendJSON(['success' => false, 'error' => 'Invalid request']);
}
$tokenHash = hash('sha256', $token);
$stmt = $pdo->prepare("
SELECT pr.user_id FROM password_resets pr
WHERE pr.token_hash = ?
AND pr.used = 0
AND pr.expires_at > NOW()
LIMIT 1
");
$stmt->execute([$tokenHash]);
$row = $stmt->fetch();
if (!$row) {
sendJSON(['success' => false, 'error' => 'Invalid or expired reset link']);
}
// Update password and mark token used
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
->execute([$newHash, $row['user_id']]);
$pdo->prepare("UPDATE password_resets SET used = 1 WHERE token_hash = ?")
->execute([$tokenHash]);
sendJSON(['success' => true]);
default:
sendJSON(['success' => false, 'error' => 'Invalid action']);
}
}
sendJSON(['success' => false, 'error' => 'Invalid request method']);
?>