feat: files 테이블 field_key 컬럼 추가 및 file_type VARCHAR 변경

- file_type: ENUM → VARCHAR(50) 변경 (확장성 개선)
- field_key: VARCHAR(100) 신규 컬럼 (비즈니스 용도 구분)
- ItemsFileController: field_key 사용, file_type 자동 분류 (detectFileType)
- File 모델: fillable에 field_key 추가
- ItemsService: getItemFiles()에서 field_key로 그룹핑
- rollback_items_migration: FK 제약조건 처리 수정
This commit is contained in:
2025-12-12 18:29:14 +09:00
parent 2e4d4d3be3
commit aa9746ae2f
5 changed files with 100 additions and 15 deletions

View File

@@ -51,13 +51,13 @@ public function index(int $id, Request $request)
// 특정 field_key만 조회 // 특정 field_key만 조회
if ($fieldKey) { if ($fieldKey) {
$query->where('file_type', $fieldKey); $query->where('field_key', $fieldKey);
} }
$files = $query->orderBy('created_at', 'desc')->get(); $files = $query->orderBy('created_at', 'desc')->get();
// field_key별 그룹핑 // field_key별 그룹핑
$grouped = $files->groupBy('file_type')->map(function ($group) { $grouped = $files->groupBy('field_key')->map(function ($group) {
return $group->map(fn ($file) => $this->formatFileResponse($file))->values(); return $group->map(fn ($file) => $this->formatFileResponse($file))->values();
}); });
@@ -115,6 +115,10 @@ public function upload(int $id, ItemFileUploadRequest $request)
// 파일 저장 (tenant 디스크) // 파일 저장 (tenant 디스크)
Storage::disk('tenant')->putFileAs($directory, $uploadedFile, $storedName); Storage::disk('tenant')->putFileAs($directory, $uploadedFile, $storedName);
// file_type 자동 분류 (MIME 타입 기반)
$mimeType = $uploadedFile->getMimeType();
$fileType = $this->detectFileType($mimeType);
// files 테이블에 저장 // files 테이블에 저장
$file = File::create([ $file = File::create([
'tenant_id' => $tenantId, 'tenant_id' => $tenantId,
@@ -122,8 +126,9 @@ public function upload(int $id, ItemFileUploadRequest $request)
'stored_name' => $storedName, 'stored_name' => $storedName,
'file_path' => $filePath, 'file_path' => $filePath,
'file_size' => $uploadedFile->getSize(), 'file_size' => $uploadedFile->getSize(),
'mime_type' => $uploadedFile->getMimeType(), 'mime_type' => $mimeType,
'file_type' => $fieldKey, // field_key 'file_type' => $fileType, // 파일 형식 (image, document, excel, archive)
'field_key' => $fieldKey, // 비즈니스 용도 (drawing, certificate 등)
'document_id' => $id, 'document_id' => $id,
'document_type' => self::ITEM_GROUP_ID, // group_id 'document_type' => self::ITEM_GROUP_ID, // group_id
'is_temp' => false, 'is_temp' => false,
@@ -215,7 +220,8 @@ private function formatFileResponse(File $file): array
'file_url' => $this->getFileUrl($file->file_path), 'file_url' => $this->getFileUrl($file->file_path),
'file_size' => $file->file_size, 'file_size' => $file->file_size,
'mime_type' => $file->mime_type, 'mime_type' => $file->mime_type,
'field_key' => $file->file_type, 'file_type' => $file->file_type,
'field_key' => $file->field_key,
'created_at' => $file->created_at?->format('Y-m-d H:i:s'), 'created_at' => $file->created_at?->format('Y-m-d H:i:s'),
]; ];
} }
@@ -227,4 +233,34 @@ private function getFileUrl(string $filePath): string
{ {
return url('/api/v1/files/download/'.base64_encode($filePath)); return url('/api/v1/files/download/'.base64_encode($filePath));
} }
/**
* MIME 타입 기반 파일 형식 분류
*/
private function detectFileType(string $mimeType): string
{
if (str_starts_with($mimeType, 'image/')) {
return 'image';
}
if (in_array($mimeType, [
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/csv',
])) {
return 'excel';
}
if (in_array($mimeType, [
'application/zip',
'application/x-rar-compressed',
'application/x-7z-compressed',
'application/gzip',
])) {
return 'archive';
}
// 기본값: document (pdf, doc, hwp 등)
return 'document';
}
} }

View File

@@ -38,6 +38,7 @@ class File extends Model
'folder_id', 'folder_id',
'is_temp', 'is_temp',
'file_type', 'file_type',
'field_key',
'document_id', 'document_id',
'document_type', 'document_type',
'file_size', 'file_size',

View File

@@ -1147,7 +1147,7 @@ private function getItemFiles(int $itemId, int $tenantId): array
return []; return [];
} }
return $files->groupBy('file_type')->map(function ($group) { return $files->groupBy('field_key')->map(function ($group) {
return $group->map(fn ($file) => [ return $group->map(fn ($file) => [
'id' => $file->id, 'id' => $file->id,
'file_name' => $file->display_name ?? $file->file_name, 'file_name' => $file->display_name ?? $file->file_name,

View File

@@ -17,26 +17,26 @@
{ {
public function up(): void public function up(): void
{ {
// 1. items 테이블 데이터 삭제 // FK 제약 조건 비활성화
if (Schema::hasTable('items')) { DB::statement('SET FOREIGN_KEY_CHECKS=0');
$itemsCount = DB::table('items')->count();
echo "items 테이블 데이터 삭제: {$itemsCount}\n";
DB::table('items')->truncate();
}
// 2. item_id_mappings 테이블 삭제 // 1. item_id_mappings 테이블 먼저 삭제 (FK 참조)
if (Schema::hasTable('item_id_mappings')) { if (Schema::hasTable('item_id_mappings')) {
$mappingsCount = DB::table('item_id_mappings')->count(); $mappingsCount = DB::table('item_id_mappings')->count();
echo "item_id_mappings 테이블 삭제: {$mappingsCount}\n"; echo "item_id_mappings 테이블 삭제: {$mappingsCount}\n";
Schema::dropIfExists('item_id_mappings'); Schema::dropIfExists('item_id_mappings');
} }
// 3. items 테이블 삭제 // 2. items 테이블 삭제
if (Schema::hasTable('items')) { if (Schema::hasTable('items')) {
echo "items 테이블 삭제\n"; $itemsCount = DB::table('items')->count();
echo "items 테이블 삭제: {$itemsCount}\n";
Schema::dropIfExists('items'); Schema::dropIfExists('items');
} }
// FK 제약 조건 재활성화
DB::statement('SET FOREIGN_KEY_CHECKS=1');
echo "✅ Items 관련 테이블 롤백 완료\n"; echo "✅ Items 관련 테이블 롤백 완료\n";
} }

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* files 테이블 수정
*
* 1. file_type: ENUM → VARCHAR(50) 변경 (확장성)
* 2. field_key: VARCHAR(100) 추가 (비즈니스 용도 구분)
*/
return new class extends Migration
{
public function up(): void
{
// 1. file_type ENUM → VARCHAR(50) 변경
DB::statement("ALTER TABLE `files` MODIFY `file_type` VARCHAR(50) NULL DEFAULT NULL COMMENT '파일 형식 (image, document, excel, archive 등)'");
// 2. field_key 컬럼 추가
if (! Schema::hasColumn('files', 'field_key')) {
Schema::table('files', function (Blueprint $table) {
$table->string('field_key', 100)
->nullable()
->after('file_type')
->comment('비즈니스 용도 구분 (drawing, certificate, specification 등)');
// 인덱스 추가 (document_type + document_id + field_key 조합 조회용)
$table->index(['document_type', 'document_id', 'field_key'], 'idx_files_document_field_key');
});
}
}
public function down(): void
{
// 1. field_key 컬럼 삭제
if (Schema::hasColumn('files', 'field_key')) {
Schema::table('files', function (Blueprint $table) {
$table->dropIndex('idx_files_document_field_key');
$table->dropColumn('field_key');
});
}
// 2. file_type VARCHAR → ENUM 복원
DB::statement("ALTER TABLE `files` MODIFY `file_type` ENUM('document','image','excel','archive') NOT NULL");
}
};