refactor(media): MediaUploadResource + ws→workspace_id + cleanup

This commit is contained in:
Paulo Castellano 2026-05-15 16:48:09 -03:00
parent 98b3a9ffb7
commit 9845d15db3
4 changed files with 49 additions and 16 deletions

View file

@ -6,6 +6,7 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\StoreUploadRequest;
use App\Http\Resources\Api\MediaUploadResource;
use App\Models\Media;
use App\Models\Workspace;
use Illuminate\Http\JsonResponse;
@ -15,20 +16,25 @@
class UploadController extends Controller
{
private const CACHE_TTL_BUFFER_SECONDS = 60;
public function store(StoreUploadRequest $request, string $token): JsonResponse
{
$expiresAt = (int) $request->query('expires');
$ttl = max(60, $expiresAt - now()->timestamp + 60);
$ttl = max(
self::CACHE_TTL_BUFFER_SECONDS,
$expiresAt - now()->timestamp + self::CACHE_TTL_BUFFER_SECONDS,
);
if (! Cache::add("mcp_upload:{$token}", true, $ttl)) {
abort(Response::HTTP_CONFLICT, 'Upload token already used.');
abort(Response::HTTP_CONFLICT);
}
if (Media::where('upload_token', $token)->exists()) {
abort(Response::HTTP_CONFLICT, 'Upload token already consumed.');
abort(Response::HTTP_CONFLICT);
}
$workspace = Workspace::findOrFail((string) $request->query('ws'));
$workspace = Workspace::findOrFail((string) $request->query('workspace_id'));
$media = DB::transaction(function () use ($workspace, $request, $token): Media {
$media = $workspace->addMedia($request->file('media'), 'assets');
@ -38,12 +44,8 @@ public function store(StoreUploadRequest $request, string $token): JsonResponse
return $media;
});
return response()->json([
'upload_token' => $token,
'media_id' => $media->id,
'type' => $media->type,
'mime_type' => $media->mime_type,
'original_filename' => $media->original_filename,
], Response::HTTP_CREATED);
return MediaUploadResource::make($media)
->response()
->setStatusCode(Response::HTTP_CREATED);
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources\Api;
use App\Models\Media;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Response shape for an MCP signed-URL upload (POST /api/uploads/{token}).
*
* @mixin Media
*/
class MediaUploadResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'upload_token' => $this->upload_token,
'media_id' => $this->id,
'type' => $this->type,
'mime_type' => $this->mime_type,
'original_filename' => $this->original_filename,
];
}
}

View file

@ -31,7 +31,7 @@ public function handle(Request $request): Response|ResponseFactory
$uploadUrl = URL::temporarySignedRoute(
'api.uploads.store',
$expiresAt,
['token' => $token, 'ws' => $workspaceId],
['token' => $token, 'workspace_id' => $workspaceId],
);
return Response::structured([

View file

@ -26,7 +26,7 @@ function signedUploadUrl(Workspace $ws, string $token, ?int $expiresInMinutes =
return URL::temporarySignedRoute(
'api.uploads.store',
now()->addMinutes($expiresInMinutes),
['token' => $token, 'ws' => $ws->id],
['token' => $token, 'workspace_id' => $ws->id],
);
}
@ -56,7 +56,7 @@ function signedUploadUrl(Workspace $ws, string $token, ?int $expiresInMinutes =
$token = (string) Str::uuid();
$file = UploadedFile::fake()->image('shot.png', 50, 50);
$response = $this->postJson(route('api.uploads.store', ['token' => $token, 'ws' => $this->workspace->id]), [
$response = $this->postJson(route('api.uploads.store', ['token' => $token, 'workspace_id' => $this->workspace->id]), [
'media' => $file,
]);
@ -70,7 +70,7 @@ function signedUploadUrl(Workspace $ws, string $token, ?int $expiresInMinutes =
$file = UploadedFile::fake()->image('shot.png', 50, 50);
$url = signedUploadUrl($this->workspace, $token);
$tampered = str_replace("ws={$this->workspace->id}", "ws={$other->id}", $url);
$tampered = str_replace("workspace_id={$this->workspace->id}", "workspace_id={$other->id}", $url);
$response = $this->postJson($tampered, ['media' => $file]);
@ -84,7 +84,7 @@ function signedUploadUrl(Workspace $ws, string $token, ?int $expiresInMinutes =
$url = URL::temporarySignedRoute(
'api.uploads.store',
now()->subMinute(),
['token' => $token, 'ws' => $this->workspace->id],
['token' => $token, 'workspace_id' => $this->workspace->id],
);
$response = $this->postJson($url, ['media' => $file]);