Files
sam-api/app/Http/Controllers/Api/V1/FileStorageController.php
권혁성 1e7a84d516 feat: [file] path 기반 presigned URL 엔드포인트 추가
- POST /api/v1/files/presigned-url-by-path 추가
- file_id 없이 image_path만 있는 레거시 데이터 지원
2026-03-20 11:24:24 +09:00

242 lines
6.1 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]);
}
/**
* R2 Presigned URL 발급 (file_path 기반, 30분 유효)
*/
public function presignedUrlByPath(Request $request)
{
$path = $request->input('path');
if (! $path) {
abort(400, 'path is required');
}
$url = Storage::disk('r2')->temporaryUrl($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();
});
}
}