Fixes
This commit is contained in:
@@ -751,24 +751,42 @@ class StoryController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
$disk = Storage::disk('public');
|
||||
$base = 'stories/' . now()->format('Y/m') . '/' . Str::uuid();
|
||||
$extension = strtolower((string) ($file->guessExtension() ?: $file->extension() ?: 'jpg'));
|
||||
$originalPath = $base . '/original.' . $extension;
|
||||
$thumbnailPath = $base . '/thumbnail.webp';
|
||||
$mediumPath = $base . '/medium.webp';
|
||||
try {
|
||||
$this->assertStoryMediaStorageIsAllowed();
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json([
|
||||
'message' => $e->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$stream = fopen($sourcePath, 'rb');
|
||||
if ($stream === false) {
|
||||
$raw = file_get_contents($sourcePath);
|
||||
if ($raw === false || $raw === '') {
|
||||
return response()->json([
|
||||
'message' => 'Unable to process uploaded image. Please try again.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$disk->put($originalPath, $stream);
|
||||
} finally {
|
||||
fclose($stream);
|
||||
$hash = hash('sha256', $raw);
|
||||
$originalExtension = strtolower((string) ($file->guessExtension() ?: $file->extension() ?: 'jpg'));
|
||||
if ($originalExtension === 'jpeg') {
|
||||
$originalExtension = 'jpg';
|
||||
}
|
||||
|
||||
$originalPath = $this->storyMediaPath('original', $hash, $hash . '.' . $originalExtension);
|
||||
$thumbnailPath = $this->storyMediaPath('sm', $hash);
|
||||
$mediumPath = $this->storyMediaPath('md', $hash);
|
||||
$disk = Storage::disk($this->storyMediaDiskName());
|
||||
|
||||
$written = $disk->put($originalPath, $raw, [
|
||||
'visibility' => 'public',
|
||||
'CacheControl' => 'public, max-age=31536000, immutable',
|
||||
'ContentType' => (string) ($file->getMimeType() ?: 'application/octet-stream'),
|
||||
]);
|
||||
|
||||
if ($written !== true) {
|
||||
return response()->json([
|
||||
'message' => 'Unable to store uploaded image. Please try again.',
|
||||
], 500);
|
||||
}
|
||||
|
||||
$storedThumbnails = false;
|
||||
@@ -781,10 +799,20 @@ class StoryController extends Controller
|
||||
$image = $manager->read($sourcePath);
|
||||
|
||||
$thumb = $image->scaleDown(width: 420);
|
||||
$disk->put($thumbnailPath, (string) $thumb->encode(new WebpEncoder(82)));
|
||||
$thumbEncoded = (string) $thumb->encode(new WebpEncoder(82));
|
||||
$disk->put($thumbnailPath, $thumbEncoded, [
|
||||
'visibility' => 'public',
|
||||
'CacheControl' => 'public, max-age=31536000, immutable',
|
||||
'ContentType' => 'image/webp',
|
||||
]);
|
||||
|
||||
$medium = $image->scaleDown(width: 1200);
|
||||
$disk->put($mediumPath, (string) $medium->encode(new WebpEncoder(85)));
|
||||
$mediumEncoded = (string) $medium->encode(new WebpEncoder(85));
|
||||
$disk->put($mediumPath, $mediumEncoded, [
|
||||
'visibility' => 'public',
|
||||
'CacheControl' => 'public, max-age=31536000, immutable',
|
||||
'ContentType' => 'image/webp',
|
||||
]);
|
||||
$storedThumbnails = true;
|
||||
} catch (\Throwable) {
|
||||
$storedThumbnails = false;
|
||||
@@ -792,17 +820,62 @@ class StoryController extends Controller
|
||||
}
|
||||
|
||||
if (! $storedThumbnails) {
|
||||
$disk->copy($originalPath, $thumbnailPath);
|
||||
$disk->copy($originalPath, $mediumPath);
|
||||
$disk->put($thumbnailPath, $raw, [
|
||||
'visibility' => 'public',
|
||||
'CacheControl' => 'public, max-age=31536000, immutable',
|
||||
'ContentType' => (string) ($file->getMimeType() ?: 'application/octet-stream'),
|
||||
]);
|
||||
$disk->put($mediumPath, $raw, [
|
||||
'visibility' => 'public',
|
||||
'CacheControl' => 'public, max-age=31536000, immutable',
|
||||
'ContentType' => (string) ($file->getMimeType() ?: 'application/octet-stream'),
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'thumbnail_url' => $disk->url($thumbnailPath),
|
||||
'medium_url' => $disk->url($mediumPath),
|
||||
'original_url' => $disk->url($originalPath),
|
||||
'thumbnail_url' => $this->storyMediaPublicUrl($thumbnailPath),
|
||||
'medium_url' => $this->storyMediaPublicUrl($mediumPath),
|
||||
'original_url' => $this->storyMediaPublicUrl($originalPath),
|
||||
]);
|
||||
}
|
||||
|
||||
private function storyMediaDiskName(): string
|
||||
{
|
||||
return (string) config('uploads.object_storage.disk', 's3');
|
||||
}
|
||||
|
||||
private function storyMediaPath(string $variant, string $hash, ?string $filename = null): string
|
||||
{
|
||||
$cleanVariant = trim($variant, '/');
|
||||
$cleanHash = strtolower((string) preg_replace('/[^a-f0-9]/', '', $hash));
|
||||
$file = $filename ?? ($cleanHash . '.webp');
|
||||
|
||||
return sprintf(
|
||||
'stories/%s/%s/%s/%s',
|
||||
$cleanVariant,
|
||||
substr($cleanHash, 0, 2),
|
||||
substr($cleanHash, 2, 2),
|
||||
ltrim($file, '/')
|
||||
);
|
||||
}
|
||||
|
||||
private function storyMediaPublicUrl(string $path): string
|
||||
{
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
private function assertStoryMediaStorageIsAllowed(): void
|
||||
{
|
||||
if (! app()->environment('production')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diskName = $this->storyMediaDiskName();
|
||||
if (in_array($diskName, ['local', 'public'], true)) {
|
||||
throw new \RuntimeException('Production story media storage must use object storage, not local/public disks.');
|
||||
}
|
||||
}
|
||||
|
||||
public function tag(string $tag): View
|
||||
{
|
||||
$storyTag = StoryTag::query()->where('slug', $tag)->firstOrFail();
|
||||
|
||||
Reference in New Issue
Block a user