The sale rotation feature provides real-time information about champions and skins currently on sale in League of Legends.
Overview
Heimerdinger.lol’s sale rotation tracker:
Displays current champion sales with discounted RP prices
Shows skin sales with discount percentages
Fetches live data from the Boris API
Implements 8-hour caching for performance
Includes a feature toggle for maintenance mode
Provides detailed error handling and logging
Route
The sale rotation uses a single route defined in routes/web.php:61:
Route :: get ( 'sale-rotation' , static fn () => ( new SaleController ) -> index ()) -> name ( 'sales.index' );
Feature Toggle
The sale rotation includes a feature toggle to enable/disable the feature:
When the feature is disabled via SALES_ENABLED=false in the .env file, the page displays a maintenance message instead of attempting to fetch sales data.
// source/app/Http/Controllers/SaleController.php:14-17
if ( ! config ( 'sales.enabled' )) {
return view ( 'sales.index' );
}
Configuration
Set the feature toggle in your .env file:
# Enable or disable fetching live Sale Rotation data from the external API.
# Set to true to enable fetching, false to show the maintenance message.
SALES_ENABLED = false
Sales Data Fetching
The SaleController::index() method (source/app/Http/Controllers/SaleController.php:12-79) fetches and caches sales data:
public function index ()
{
if ( ! config ( 'sales.enabled' )) {
return view ( 'sales.index' );
}
try {
$sales = Cache :: remember ( 'sales_data' , 60 * 60 * 8 , function () {
$borisUrl = rtrim (( string ) config ( 'services.boris.url' ), '/' );
$borisApiKey = ( string ) config ( 'services.boris.api_key' );
$headers = [
'X-API-Key' => $borisApiKey ,
];
$championSalesResponse = Http :: withHeaders ( $headers ) -> get ( $borisUrl . '/api/champions/sales' );
$skinSalesResponse = Http :: withHeaders ( $headers ) -> get ( $borisUrl . '/api/skins/sales' );
// Validation and error handling...
});
} catch ( \ Exception $exception ) {
// Error handling...
}
return view ( 'sales.index' , [ 'sales' => $sales ]);
}
API Endpoints
The sale controller fetches data from two Boris API endpoints:
Champion Sales GET /api/champions/salesReturns champions currently on sale
Skin Sales GET /api/skins/salesReturns skins currently on sale
Authentication
Both endpoints require the X-API-Key header with your Boris API key from config('services.boris.api_key').
The API returns sales data in the following format:
{
"items" : [
{
"id" : 1 ,
"sale_rp" : 487 ,
"percent_off" : 35
}
]
}
This is transformed into:
[
'champion_sales' => [
[
'item_id' => 1 ,
'rp' => 487 ,
'percent_off' => 35
]
],
'skin_sales' => [
[
'item_id' => 53 ,
'rp' => 675 ,
'percent_off' => 25
]
]
]
Caching Strategy
Sales data is cached for 8 hours (28,800 seconds) to balance freshness with API rate limits.
$sales = Cache :: remember ( 'sales_data' , 60 * 60 * 8 , function () {
// Fetch sales data from API
});
Cache Key
The cache uses a simple key: sales_data
Error Handling
The sale controller implements comprehensive error handling:
HTTP Status Validation
Checks if both API requests returned successful status codes (200) if ( ! $championSalesResponse -> successful () || ! $skinSalesResponse -> successful ()) {
Log :: error ( 'Boris sales request failed.' );
throw new RuntimeException ( 'Boris sales request failed.' );
}
Payload Validation
Verifies the response contains the expected items array if (
! isset ( $championSales [ 'items' ]) || ! is_array ( $championSales [ 'items' ]) ||
! isset ( $skinSales [ 'items' ]) || ! is_array ( $skinSales [ 'items' ])
) {
Log :: error ( 'Boris sales payload is invalid.' );
throw new RuntimeException ( 'Boris sales payload is invalid.' );
}
Exception Handling
Returns appropriate HTTP error codes to the user
503 Service Unavailable - When Boris API fails or returns invalid data
500 Internal Server Error - For unexpected errors
Error Logging
All errors are logged with contextual information:
Log :: error ( 'Boris sales request failed.' , [
'champions_status' => $championSalesResponse -> status (),
'skins_status' => $skinSalesResponse -> status (),
]);
Errors are also sent to Discord webhooks if configured (see LOG_DISCORD_WEBHOOK_URL in .env).
Configuration Requirements
To use the sale rotation feature, configure these environment variables:
Enable/disable the sale rotation feature
Base URL for the Boris API (e.g., https://boris.heimerdinger.lol)
API key for authenticating with the Boris API
Maintenance Mode
When SALES_ENABLED=false, the sale rotation page displays a maintenance message instead of attempting to fetch data:
if ( ! config ( 'sales.enabled' )) {
return view ( 'sales.index' );
}
The view is returned without any sales data, allowing the template to display a user-friendly maintenance message.
Champions Browse all champions to see which ones are on sale
Skins View the full skin catalog with rarity information
Data Sources Learn about the Boris API and data fetching strategy
Controller API View the full SaleController API reference