refactor(media): MediaUploadResource + ws→workspace_id + cleanup
This commit is contained in:
parent
98b3a9ffb7
commit
9845d15db3
4 changed files with 49 additions and 16 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
app/Http/Resources/Api/MediaUploadResource.php
Normal file
31
app/Http/Resources/Api/MediaUploadResource.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue