Files
sam-api/app/Http/Controllers/Api/V1/FileStorageController.php
권혁성 9bdb81d8ff fix: presignedUrl에서 R2 exists() 체크 제거
- exists()가 매 요청마다 R2 HTTP HEAD 호출 → 개발서버에서 지연/500 발생
- temporaryUrl()은 로컬 서명 생성만 하므로 R2 접근 불필요
- 파일 미존재 시 브라우저가 R2에서 직접 404 수신

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:17:53 +09:00

227 lines
5.7 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\FileMoveRequest;
use App\Http\Requests\Api\V1\FileUploadRequest;
use App\Http\Requests\Api\V1\ShareLinkRequest;
use App\Services\FileStorageService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileStorageController extends Controller
{
/**
* Bearer 토큰 없이 X-TENANT-ID 헤더로 컨텍스트 설정 (MNG 프록시 호출용)
*/
private function ensureContext(Request $request): void
{
if (! app()->bound('tenant_id') || ! app('tenant_id')) {
$tenantId = (int) ($request->header('X-TENANT-ID') ?: 287);
app()->instance('tenant_id', $tenantId);
}
if (! app()->bound('api_user') || ! app('api_user')) {
app()->instance('api_user', 1);
}
}
/**
* Upload file to temp
*/
public function upload(FileUploadRequest $request)
{
return ApiResponse::handle(function () use ($request) {
$service = new FileStorageService;
$file = $service->upload(
$request->file('file'),
$request->input('description')
);
return $file;
}, __('message.file_uploaded'));
}
/**
* Move files from temp to folder
*/
public function move(FileMoveRequest $request)
{
return ApiResponse::handle(function () use ($request) {
$service = new FileStorageService;
$files = $service->moveToFolder(
$request->input('file_ids'),
$request->input('folder_id'),
$request->input('document_id'),
$request->input('document_type')
);
return $files;
}, __('message.files_moved'));
}
/**
* Get file by ID
*/
public function show(int $id)
{
return ApiResponse::handle(function () use ($id) {
$service = new FileStorageService;
return $service->getFile($id);
});
}
/**
* List files
*/
public function index(Request $request)
{
return ApiResponse::handle(function () use ($request) {
$service = new FileStorageService;
return $service->listFiles($request->all());
});
}
/**
* Get trash files
*/
public function trash()
{
return ApiResponse::handle(function () {
$service = new FileStorageService;
return $service->getTrash();
});
}
/**
* Download file (attachment)
*/
public function download(int $id, Request $request)
{
$this->ensureContext($request);
$service = new FileStorageService;
$file = $service->getFile($id);
return $file->download(inline: false);
}
/**
* View file inline (이미지/PDF 브라우저에서 바로 표시)
*/
public function view(int $id, Request $request)
{
$this->ensureContext($request);
$service = new FileStorageService;
$file = $service->getFile($id);
return $file->download(inline: true);
}
/**
* R2 Presigned URL 발급 (30분 유효)
*/
public function presignedUrl(int $id, Request $request)
{
$this->ensureContext($request);
$service = new FileStorageService;
$file = $service->getFile($id);
if (! $file->file_path) {
abort(404, 'File not found');
}
$url = Storage::disk('r2')->temporaryUrl(
$file->file_path,
now()->addMinutes(30)
);
return ApiResponse::handle(fn () => ['url' => $url]);
}
/**
* Soft delete file
*/
public function destroy(int $id)
{
return ApiResponse::handle(function () use ($id) {
$service = new FileStorageService;
return $service->deleteFile($id);
}, __('message.file_deleted'));
}
/**
* Restore file from trash
*/
public function restore(int $id)
{
return ApiResponse::handle(function () use ($id) {
$service = new FileStorageService;
return $service->restoreFile($id);
}, __('message.file_restored'));
}
/**
* Permanently delete file
*/
public function permanentDelete(int $id)
{
return ApiResponse::handle(function () use ($id) {
$service = new FileStorageService;
$service->permanentDelete($id);
return ['success' => true];
}, __('message.file_permanently_deleted'));
}
/**
* Create share link
*/
public function createShareLink(ShareLinkRequest $request)
{
return ApiResponse::handle(function () use ($request) {
$service = new FileStorageService;
$link = $service->createShareLink(
$request->input('file_id'),
$request->input('expiry_hours', 24)
);
return [
'token' => $link->token,
'url' => url("/api/v1/files/share/{$link->token}"),
'expires_at' => $link->expires_at,
];
}, __('message.share_link_created'));
}
/**
* Download file by share token (public, no auth)
*/
public function downloadShared(string $token)
{
$file = FileStorageService::getFileByShareToken($token);
return $file->download();
}
/**
* Get storage usage
*/
public function storageUsage()
{
return ApiResponse::handle(function () {
$service = new FileStorageService;
return $service->getStorageUsage();
});
}
}