Wire admin studio SSR and search infrastructure
This commit is contained in:
@@ -6,8 +6,10 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkDownload;
|
||||
use App\Services\ArtworkOriginalFileLocator;
|
||||
use App\Services\ArtworkStatsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -38,16 +40,18 @@ final class ArtworkDownloadController extends Controller
|
||||
|
||||
public function __construct(
|
||||
private readonly ArtworkStatsService $stats,
|
||||
private readonly ArtworkOriginalFileLocator $originalFiles,
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request, int $id): BinaryFileResponse
|
||||
public function __invoke(Request $request, int $id): BinaryFileResponse|Response
|
||||
{
|
||||
$artwork = Artwork::query()->find($id);
|
||||
|
||||
if (! $artwork) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$filePath = $this->resolveOriginalPath($artwork);
|
||||
$filePath = $this->originalFiles->resolveLocalPath($artwork);
|
||||
$ext = strtolower(ltrim((string) pathinfo($filePath, PATHINFO_EXTENSION), '.'));
|
||||
|
||||
if ($filePath === '' || ! in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
|
||||
@@ -76,36 +80,59 @@ final class ArtworkDownloadController extends Controller
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
||||
$downloadName = $this->buildDownloadFilename((string) $artwork->file_name, $ext);
|
||||
|
||||
// X-Accel-Redirect is safe only when nginx is explicitly configured to
|
||||
// map the internal URI to the originals root. Otherwise fallback to the
|
||||
// normal Laravel download response.
|
||||
$accelUri = $this->resolveAccelUri($filePath);
|
||||
if ($accelUri !== null) {
|
||||
return response('', 200, [
|
||||
'X-Accel-Redirect' => $accelUri,
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . addslashes($downloadName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->download($filePath, $downloadName);
|
||||
}
|
||||
|
||||
private function resolveOriginalPath(Artwork $artwork): string
|
||||
private function resolveAccelUri(string $filePath): ?string
|
||||
{
|
||||
$relative = trim((string) $artwork->file_path, '/');
|
||||
$prefix = trim((string) config('uploads.object_storage.prefix', 'artworks'), '/') . '/original/';
|
||||
|
||||
if ($relative !== '' && str_starts_with($relative, $prefix)) {
|
||||
$suffix = substr($relative, strlen($prefix));
|
||||
$root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR);
|
||||
|
||||
return $root . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, (string) $suffix);
|
||||
if (! config('app.download_accel_enabled')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hash = strtolower((string) $artwork->hash);
|
||||
$ext = strtolower(ltrim((string) $artwork->file_ext, '.'));
|
||||
if (! $this->isValidHash($hash) || $ext === '') {
|
||||
return '';
|
||||
$accelBase = rtrim((string) config('app.download_accel_path', ''), '/');
|
||||
if ($accelBase === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR);
|
||||
if ($root === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $root
|
||||
. DIRECTORY_SEPARATOR . substr($hash, 0, 2)
|
||||
. DIRECTORY_SEPARATOR . substr($hash, 2, 2)
|
||||
. DIRECTORY_SEPARATOR . $hash . '.' . $ext;
|
||||
$normalizedRoot = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $root);
|
||||
$normalizedFilePath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $filePath);
|
||||
$rootPrefix = $normalizedRoot . DIRECTORY_SEPARATOR;
|
||||
|
||||
if (! str_starts_with($normalizedFilePath, $rootPrefix)) {
|
||||
Log::warning('Artwork download accel path skipped because file is outside originals root.', [
|
||||
'resolved_path' => $filePath,
|
||||
'originals_root' => $root,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$relativePath = substr($normalizedFilePath, strlen($normalizedRoot));
|
||||
if ($relativePath === false || $relativePath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $accelBase . str_replace(DIRECTORY_SEPARATOR, '/', $relativePath);
|
||||
}
|
||||
|
||||
private function recordDownload(Request $request, int $artworkId): void
|
||||
@@ -139,11 +166,6 @@ final class ArtworkDownloadController extends Controller
|
||||
Artwork::query()->whereKey($artworkId)->increment('download_count');
|
||||
}
|
||||
|
||||
private function isValidHash(string $hash): bool
|
||||
{
|
||||
return $hash !== '' && preg_match('/^[a-f0-9]+$/', $hash) === 1;
|
||||
}
|
||||
|
||||
private function buildDownloadFilename(string $fileName, string $ext): string
|
||||
{
|
||||
$name = trim($fileName);
|
||||
|
||||
Reference in New Issue
Block a user