## Analytics — Live Activity Feed - Dedicated /stats/live standalone page (mirrors /stats/logs pattern) - Consistent 3-tab navigation across Statistics / Live Feed / Visit Logs - checkForUpdates() with skipRender(): O(1) MAX(id) poll — ~100-byte response on no-change - latestVisitId sentinel (-1) prevents unnecessary first-poll re-render - Cache key versioned by latestVisitId — fixes bug where concurrent users could permanently miss a visit due to stale thundering-herd cache - 3-second thundering-herd cache (key: url_id + date + filters + latestVisitId) - Country flags via flagcdn.com (precomputed in PHP, Alpine.js CSP-safe onerror) - time_ago precomputed in PHP — no Carbon::parse() double-instantiation in Blade - SELECT only 12 needed columns instead of SELECT *; limit 25 rows - wire:poll.5s.visible — polling stops when widget is off-screen ## Link Organization - Folders: one folder per link, clickable → filtered link list, link count badge - Tags: up to 5 tags per link, clickable → filtered link list, link count badge - Archiving: soft-archive links instead of permanent deletion; restorable ## REST API - Per-key API scopes: links:read-only (GET) / links:read-write (full CRUD) - Per-key rate limiting: individual requests/minute per API key ## Translations - Added stats_live_feed_poll_interval (EN: 'Updates every 5s', PL: 'Aktualizacja co 5s') ## Docs - README.md: corrected API scopes/rate-limiting docs (features were already implemented) - README.md: added Folders, Tags, Archiving to features list - README.md: added v5.0.0 changelog entry
49 lines
2.1 KiB
PHP
49 lines
2.1 KiB
PHP
<?php
|
|
|
|
use Bjanczak\FilamentShortUrl\Http\Controllers\ShortUrlApiController;
|
|
use Bjanczak\FilamentShortUrl\Http\Controllers\ShortUrlLogoController;
|
|
use Bjanczak\FilamentShortUrl\Http\Controllers\ShortUrlRedirectController;
|
|
use Bjanczak\FilamentShortUrl\Http\Middleware\AuthenticateShortUrlApi;
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
Route::get('/.well-known/apple-app-site-association', [ShortUrlRedirectController::class, 'serveAasa'])
|
|
->middleware(['web']);
|
|
Route::get('/apple-app-site-association', [ShortUrlRedirectController::class, 'serveAasa'])
|
|
->middleware(['web']);
|
|
Route::get('/.well-known/assetlinks.json', [ShortUrlRedirectController::class, 'serveAssetLinks'])
|
|
->middleware(['web']);
|
|
|
|
Route::match(
|
|
['GET', 'POST'],
|
|
config('filament-short-url.route_prefix', 's').'/{key}',
|
|
ShortUrlRedirectController::class
|
|
)
|
|
->name('short-url.redirect')
|
|
->where('key', '[a-zA-Z0-9_-]+')
|
|
->middleware(config('filament-short-url.middleware', ['web', 'throttle:120,1']));
|
|
|
|
Route::prefix('api/short-url')
|
|
->middleware([
|
|
AuthenticateShortUrlApi::class,
|
|
])
|
|
->group(function () {
|
|
Route::get('links', [ShortUrlApiController::class, 'index']);
|
|
Route::post('links', [ShortUrlApiController::class, 'store']);
|
|
Route::get('links/{idOrKey}', [ShortUrlApiController::class, 'show']);
|
|
Route::get('links/{idOrKey}/stats', [ShortUrlApiController::class, 'stats']);
|
|
Route::match(['PUT', 'PATCH'], 'links/{idOrKey}', [ShortUrlApiController::class, 'update']);
|
|
Route::delete('links/{idOrKey}', [ShortUrlApiController::class, 'destroy']);
|
|
});
|
|
|
|
Route::post('admin/short-url/upload-logo', [ShortUrlLogoController::class, 'uploadLogo'])
|
|
->name('short-url.upload-logo')
|
|
->middleware(['web']);
|
|
|
|
Route::get('short-url/logo/{filename}', [ShortUrlLogoController::class, 'serveLogo'])
|
|
->name('short-url.logo');
|
|
|
|
if (config('filament-short-url.enable_fallback_route', true)) {
|
|
Route::fallback(ShortUrlRedirectController::class)
|
|
->middleware(config('filament-short-url.middleware', ['web', 'throttle:120,1']));
|
|
}
|