These helpers were no longer called after the v2 migration rewrote processLogEntries and fetchAndStoreLogPage (the latter now prepares its own idempotent INSERT inline). Rendered unused by the v2 migration.
279 lines
8.7 KiB
PHP
279 lines
8.7 KiB
PHP
<?php
|
|
/**
|
|
* Utility functions for the Torn Vault Tracker application.
|
|
*
|
|
* This file contains helper functions for database operations, API calls,
|
|
* and vault record management.
|
|
*
|
|
* PHP version 8.1+
|
|
*
|
|
* @category Utility
|
|
* @package TornVaultTracker
|
|
* @author Keith Solomon <ksolomon@gmail.com>
|
|
* @license Unlicense https://unlicense.org/
|
|
* @link https://github.com/ksolomon/Torn-Vault-Tracker
|
|
*/
|
|
|
|
/**
|
|
* Checks if the database is new and empty.
|
|
*
|
|
* This function checks if the 'vault' table in the database has any entries.
|
|
* If it does not, it returns true, indicating that the database is new and
|
|
* empty. Otherwise, it returns false.
|
|
*
|
|
* @return boolean True if the database is new and empty, false otherwise.
|
|
*/
|
|
function dbNew() {
|
|
$pdo = getDatabaseConnection();
|
|
$stmt = $pdo->query("SELECT COUNT(*) FROM vault");
|
|
|
|
return $stmt->fetchColumn() == 0;
|
|
}
|
|
|
|
/**
|
|
* Ensures that a user has an API key configured.
|
|
*
|
|
* @param string $user The user to check the API key for
|
|
*
|
|
* @return void
|
|
*
|
|
* @throws Exception If the user does not have an API key configured
|
|
*/
|
|
function ensureUserHasApiKey($user) {
|
|
if (!array_key_exists($user, USER_KEYS)) {
|
|
throw new ApiKeyMissingException("User does not have an API key configured.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes a GET request to the given API endpoint and returns the JSON response as an associative array.
|
|
*
|
|
* Uses the v2 Torn API authentication scheme by sending the API key in an
|
|
* `Authorization: ApiKey <key>` header. The key is never transmitted as a
|
|
* query parameter.
|
|
*
|
|
* @param string $apiEndpoint The URL of the API endpoint to call
|
|
* @param string $apiKey The v2 API key to authenticate with
|
|
*
|
|
* @throws ApiKeyMissingException If the API key is empty
|
|
* @throws CurlErrorException If the cURL call fails
|
|
* @throws JsonDataException If the response body cannot be decoded as JSON
|
|
*
|
|
* @return array The JSON response from the API
|
|
*/
|
|
function executeApiCall($apiEndpoint, $apiKey) {
|
|
if (empty($apiKey)) {
|
|
throw new ApiKeyMissingException('API key is required for executeApiCall.');
|
|
}
|
|
|
|
$headers = ["Authorization: ApiKey $apiKey"];
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $apiEndpoint);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
|
|
$response = curl_exec($ch);
|
|
|
|
if (curl_errno($ch)) {
|
|
throw new CurlErrorException("cURL error: " . curl_error($ch));
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
$responseData = json_decode($response, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new JsonDataException("Failed to decode JSON response: " . json_last_error_msg());
|
|
}
|
|
|
|
return $responseData;
|
|
}
|
|
|
|
/**
|
|
* Validates the API response data for the presence of a log array.
|
|
*
|
|
* @param array $responseData The API response data to validate.
|
|
*
|
|
* @return void
|
|
*
|
|
* @throws Exception If the log data is missing or not an array.
|
|
*/
|
|
function validateApiResponse($responseData) {
|
|
if (!isset($responseData['log']) || !is_array($responseData['log'])) {
|
|
throw new ApiValidationException('Invalid log data received from the API.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process an array of log entries retrieved from the Torn v2 API.
|
|
*
|
|
* For each entry, extracts the v2 shape (`id`, `timestamp`,
|
|
* `details.title`, `data.deposited` | `data.withdrawn`) and inserts it
|
|
* via the prepared statement, which uses `ON CONFLICT(id) DO NOTHING`
|
|
* for idempotency.
|
|
*
|
|
* @param array $logEntries The array of v2 log entries to process
|
|
* @param string $user The user whose log entries are being processed
|
|
* @param PDOStatement $insertStmt A prepared statement for the idempotent
|
|
* insert (`INSERT … ON CONFLICT(id) DO NOTHING`)
|
|
*
|
|
* @throws LogEntryIncompleteException If a vault entry is missing timestamp
|
|
* or details.title
|
|
*
|
|
* @return void
|
|
*/
|
|
function processLogEntries($logEntries, $user, $insertStmt) {
|
|
foreach ($logEntries as $entry) {
|
|
$id = $entry['id'] ?? null;
|
|
|
|
if (!$id) {
|
|
consoleLog('Skipping entry with no id: ' . print_r($entry, true));
|
|
continue;
|
|
}
|
|
|
|
$timestamp = $entry['timestamp'] ?? null;
|
|
$description = $entry['details']['title'] ?? null;
|
|
$hasDeposit = isset($entry['data']['deposited']);
|
|
$hasWithdraw = isset($entry['data']['withdrawn']);
|
|
|
|
if ($hasDeposit) {
|
|
$amount = (int)$entry['data']['deposited'];
|
|
} elseif ($hasWithdraw) {
|
|
$amount = -((int)$entry['data']['withdrawn']);
|
|
} else {
|
|
consoleLog('Skipping entry ' . $id . ' with no deposited/withdrawn: ' . print_r($entry, true));
|
|
continue;
|
|
}
|
|
|
|
if ($timestamp === null || $description === null) {
|
|
throw new LogEntryIncompleteException(
|
|
"Entry $id missing timestamp or details.title."
|
|
);
|
|
}
|
|
|
|
$insertStmt->bindValue(':id', $id);
|
|
$insertStmt->bindValue(':user', $user);
|
|
$insertStmt->bindValue(':timestamp', $timestamp);
|
|
$insertStmt->bindValue(':description', $description);
|
|
$insertStmt->bindValue(':amount', $amount);
|
|
$insertStmt->execute();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch vault records from the database
|
|
* If a user is provided, only that user's records
|
|
* are returned. Otherwise, all records are returned.
|
|
*
|
|
* @param string $user (optional) The user to filter by
|
|
*
|
|
* @return array An array of records, or an empty array on error
|
|
*/
|
|
function fetchVaultRecords($user = null) {
|
|
$pdo = getDatabaseConnection();
|
|
|
|
if ($user) {
|
|
$query = "SELECT * FROM vault WHERE user = :user ORDER BY timestamp DESC";
|
|
$params = [':user' => $user];
|
|
} else {
|
|
$query = "SELECT * FROM vault ORDER BY timestamp DESC";
|
|
$params = [];
|
|
}
|
|
|
|
try {
|
|
$stmt = $pdo->prepare($query);
|
|
$stmt->execute($params);
|
|
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (PDOException $e) {
|
|
echo "Error fetching records: " . $e->getMessage();
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iterate over all vault records for the given user (or all users if no user is given),
|
|
*
|
|
* @param string|null $name (optional) The user to loop over records for.
|
|
* If not provided, all users' records will be looped over.
|
|
*
|
|
* @return int The total balance of the given user (or all users if no user is given).
|
|
*/
|
|
function vaultLoop ($name=null) {
|
|
$vault = fetchVaultRecords($name);
|
|
$balance = 0;
|
|
|
|
foreach ($vault as $entry) {
|
|
$balance += $entry['amount'];
|
|
}
|
|
|
|
return $balance;
|
|
}
|
|
|
|
/**
|
|
* Returns a string indicating the sign of a number, with a dollar symbol.
|
|
*
|
|
* @param int|float $number The number to check.
|
|
*
|
|
* @return string A string containing a dollar sign and a negative sign if the
|
|
* number is negative.
|
|
*/
|
|
function getSign($number) {
|
|
if (substr($number, 0, 1) == '-') {
|
|
return '-$';
|
|
}
|
|
|
|
return '$';
|
|
}
|
|
|
|
/**
|
|
* Fetch a single page of log entries from the Torn v2 API and store them.
|
|
*
|
|
* Performs one HTTP request, validates the response, inserts each vault
|
|
* log entry (idempotently via INSERT ... ON CONFLICT), and returns the
|
|
* pagination cursor (`_metadata.links.next`) if more pages remain.
|
|
*
|
|
* @param PDO $pdo Database connection used to insert vault entries.
|
|
* @param string $user The user whose log entries are being fetched.
|
|
* @param string $url Full URL for the v2 API request.
|
|
*
|
|
* @throws ApiKeyMissingException If no API key is configured for the user.
|
|
* @throws CurlErrorException If the HTTP request fails.
|
|
* @throws JsonDataException If the response body is not valid JSON.
|
|
* @throws ApiValidationException If the response is missing the `log` array.
|
|
*
|
|
* @return string|null The `_metadata.links.next` URL, or null when there are no more pages.
|
|
*/
|
|
function fetchAndStoreLogPage($pdo, $user, $url) {
|
|
if (!array_key_exists($user, USER_KEYS)) {
|
|
throw new ApiKeyMissingException("User does not have an API key configured.");
|
|
}
|
|
|
|
$responseData = executeApiCall($url, USER_KEYS[$user]);
|
|
validateApiResponse($responseData);
|
|
|
|
$insertStmt = $pdo->prepare(
|
|
'INSERT INTO vault (id, user, timestamp, description, amount) '
|
|
. 'VALUES (:id, :user, :timestamp, :description, :amount) '
|
|
. 'ON CONFLICT(id) DO NOTHING'
|
|
);
|
|
|
|
processLogEntries($responseData['log'], $user, $insertStmt);
|
|
|
|
return $responseData['_metadata']['links']['next'] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Print a variable to the console for debugging purposes.
|
|
*
|
|
* @param mixed $data The data to print to the console.
|
|
*
|
|
* @return void
|
|
*/
|
|
function consoleLog( $data ) {
|
|
echo '<script>';
|
|
echo 'console.log(' . json_encode($data) . ')';
|
|
echo '</script>';
|
|
}
|