getProductByCode($code); $validated = $request->validated(); $fileType = $request->route('type') ?? $validated['type']; $file = $validated['file']; // 파일 저장 경로: items/{code}/{type}/{filename} $directory = sprintf('items/%s/%s', $code, $fileType); $filePath = Storage::disk('public')->putFile($directory, $file); $fileUrl = Storage::disk('public')->url($filePath); $originalName = $file->getClientOriginalName(); // Product 모델 업데이트 $updateData = $this->buildUpdateData($fileType, $filePath, $originalName, $validated); $product->update($updateData); return [ 'file_type' => $fileType, 'file_url' => $fileUrl, 'file_path' => $filePath, 'file_name' => $originalName, 'product' => $product->fresh(), ]; }, __('message.file.uploaded')); } /** * 파일 삭제 * * DELETE /api/v1/items/{code}/files/{type} */ public function delete(string $code, string $type, Request $request) { return ApiResponse::handle(function () use ($code, $type) { $product = $this->getProductByCode($code); // 파일 타입 검증 if (! in_array($type, ['bending_diagram', 'specification', 'certification'])) { throw new \InvalidArgumentException(__('error.file.invalid_file_type')); } // 파일 경로 가져오기 $filePath = $this->getFilePath($product, $type); if ($filePath) { // 물리적 파일 삭제 Storage::disk('public')->delete($filePath); } // DB 필드 null 처리 $updateData = $this->buildDeleteData($type); $product->update($updateData); return [ 'file_type' => $type, 'deleted' => (bool) $filePath, 'product' => $product->fresh(), ]; }, __('message.file.deleted')); } /** * 코드로 Product 조회 */ private function getProductByCode(string $code): Product { $tenantId = app('tenant_id'); $product = Product::query() ->where('tenant_id', $tenantId) ->where('code', $code) ->first(); if (! $product) { throw new NotFoundHttpException(__('error.not_found')); } return $product; } /** * 파일 타입별 업데이트 데이터 구성 */ private function buildUpdateData(string $fileType, string $filePath, string $originalName, array $validated): array { $updateData = match ($fileType) { 'bending_diagram' => [ 'bending_diagram' => $filePath, 'bending_details' => $validated['bending_details'] ?? null, ], 'specification' => [ 'specification_file' => $filePath, 'specification_file_name' => $originalName, ], 'certification' => [ 'certification_file' => $filePath, 'certification_file_name' => $originalName, 'certification_number' => $validated['certification_number'] ?? null, 'certification_start_date' => $validated['certification_start_date'] ?? null, 'certification_end_date' => $validated['certification_end_date'] ?? null, ], default => throw new \InvalidArgumentException(__('error.file.invalid_file_type')), }; return $updateData; } /** * 파일 타입별 삭제 데이터 구성 */ private function buildDeleteData(string $fileType): array { return match ($fileType) { 'bending_diagram' => [ 'bending_diagram' => null, 'bending_details' => null, ], 'specification' => [ 'specification_file' => null, 'specification_file_name' => null, ], 'certification' => [ 'certification_file' => null, 'certification_file_name' => null, 'certification_number' => null, 'certification_start_date' => null, 'certification_end_date' => null, ], default => throw new \InvalidArgumentException(__('error.file.invalid_file_type')), }; } /** * Product에서 파일 경로 가져오기 */ private function getFilePath(Product $product, string $fileType): ?string { return match ($fileType) { 'bending_diagram' => $product->bending_diagram, 'specification' => $product->specification_file, 'certification' => $product->certification_file, default => null, }; } }