Add performance, security, and reliability improvements
- Consolidate all 20 API files to use centralized Database helper - Add optimistic locking to ticket updates to prevent concurrent conflicts - Add caching to StatsModel (60s TTL) for dashboard performance - Add health check endpoint (api/health.php) for monitoring - Improve rate limit cleanup with cron script and efficient DirectoryIterator - Enable rate limit response headers (X-RateLimit-*) - Add audit logging for workflow transitions - Log Discord webhook failures instead of silencing - Fix visibility check on export_tickets.php - Add database migration system with performance indexes - Fix cron recurring tickets to use assignTicket method Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
97
cron/cleanup_ratelimit.php
Normal file
97
cron/cleanup_ratelimit.php
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Rate Limit Cleanup Cron Job
|
||||
*
|
||||
* Cleans up expired rate limit files from the temp directory.
|
||||
* Should be run via cron every 5-10 minutes:
|
||||
* */5 * * * * /usr/bin/php /path/to/cron/cleanup_ratelimit.php
|
||||
*
|
||||
* This script can also be run manually for immediate cleanup.
|
||||
*/
|
||||
|
||||
// Prevent web access
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit('CLI access only');
|
||||
}
|
||||
|
||||
// Configuration
|
||||
$rateLimitDir = sys_get_temp_dir() . '/tinker_tickets_ratelimit';
|
||||
$lockFile = $rateLimitDir . '/.cleanup.lock';
|
||||
$maxAge = 120; // 2 minutes (2x the rate limit window)
|
||||
$maxLockAge = 300; // 5 minutes - release stale locks
|
||||
|
||||
// Check if directory exists
|
||||
if (!is_dir($rateLimitDir)) {
|
||||
echo "Rate limit directory does not exist: {$rateLimitDir}\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Acquire lock to prevent concurrent cleanups
|
||||
if (file_exists($lockFile)) {
|
||||
$lockAge = time() - filemtime($lockFile);
|
||||
if ($lockAge < $maxLockAge) {
|
||||
echo "Cleanup already in progress (lock age: {$lockAge}s)\n";
|
||||
exit(0);
|
||||
}
|
||||
// Stale lock, remove it
|
||||
@unlink($lockFile);
|
||||
}
|
||||
|
||||
// Create lock file
|
||||
if (!@touch($lockFile)) {
|
||||
echo "Could not create lock file\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$deleted = 0;
|
||||
$scanned = 0;
|
||||
$errors = 0;
|
||||
|
||||
try {
|
||||
$iterator = new DirectoryIterator($rateLimitDir);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isDot() || !$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip lock file and non-json files
|
||||
$filename = $file->getFilename();
|
||||
if ($filename === '.cleanup.lock' || !str_ends_with($filename, '.json')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scanned++;
|
||||
|
||||
// Check file age
|
||||
$fileAge = $now - $file->getMTime();
|
||||
if ($fileAge > $maxAge) {
|
||||
$filepath = $file->getPathname();
|
||||
if (@unlink($filepath)) {
|
||||
$deleted++;
|
||||
} else {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "Error during cleanup: " . $e->getMessage() . "\n";
|
||||
@unlink($lockFile);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Release lock
|
||||
@unlink($lockFile);
|
||||
|
||||
// Output results
|
||||
echo "Rate limit cleanup completed:\n";
|
||||
echo " - Scanned: {$scanned} files\n";
|
||||
echo " - Deleted: {$deleted} expired files\n";
|
||||
if ($errors > 0) {
|
||||
echo " - Errors: {$errors} files could not be deleted\n";
|
||||
}
|
||||
|
||||
exit($errors > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user