'RS256', 'typ' => 'JWT'])); $jwtClaim = base64_encode(json_encode([ 'iss' => $serviceAccount['client_email'], 'scope' => 'https://www.googleapis.com/auth/devstorage.full_control', 'aud' => 'https://oauth2.googleapis.com/token', 'exp' => $now + 3600, 'iat' => $now ])); $privateKey = openssl_pkey_get_private($serviceAccount['private_key']); if (!$privateKey) { error_log('GCS 삭제 실패: 개인 키 읽기 오류'); return false; } openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); openssl_free_key($privateKey); $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature); // OAuth 토큰 요청 $tokenCh = curl_init('https://oauth2.googleapis.com/token'); curl_setopt($tokenCh, CURLOPT_POST, true); curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt ])); curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true); curl_setopt($tokenCh, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); $tokenResponse = curl_exec($tokenCh); $tokenCode = curl_getinfo($tokenCh, CURLINFO_HTTP_CODE); curl_close($tokenCh); if ($tokenCode !== 200) { error_log('GCS 삭제 실패: OAuth 토큰 요청 실패 (HTTP ' . $tokenCode . ')'); return false; } $tokenData = json_decode($tokenResponse, true); if (!isset($tokenData['access_token'])) { error_log('GCS 삭제 실패: OAuth 토큰 없음'); return false; } $accessToken = $tokenData['access_token']; // GCS에서 파일 삭제 $delete_url = 'https://storage.googleapis.com/storage/v1/b/' . urlencode($bucket_name) . '/o/' . urlencode($object_name); $ch = curl_init($delete_url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 204 No Content 또는 404 Not Found는 성공으로 간주 if ($code === 204 || $code === 404) { return true; } else { error_log('GCS 삭제 실패 (HTTP ' . $code . '): ' . $response); return false; } } // 출력 버퍼 비우기 ob_clean(); header('Content-Type: application/json; charset=utf-8'); // 권한 체크 if (!isset($user_id) || $level > 5) { echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']); exit; } // 업무협의 ID 확인 $consultation_id = isset($_POST['id']) ? intval($_POST['id']) : 0; $manager_id = $user_id; if ($consultation_id <= 0) { echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']); exit; } try { $pdo = db_connect(); // 녹음 파일 정보 조회 $sql = "SELECT audio_file_path FROM manager_consultations WHERE id = ? AND manager_id = ?"; $stmt = $pdo->prepare($sql); $stmt->execute([$consultation_id, $manager_id]); $consultation = $stmt->fetch(PDO::FETCH_ASSOC); if (!$consultation) { echo json_encode(['success' => false, 'message' => '녹음 파일을 찾을 수 없습니다.']); exit; } // 1. 서버 파일 삭제 if (!empty($consultation['audio_file_path'])) { // GCS URI가 아닌 경우 로컬 파일 삭제 if (strpos($consultation['audio_file_path'], 'gs://') !== 0) { $file_path = $_SERVER['DOCUMENT_ROOT'] . $consultation['audio_file_path']; $file_path = str_replace('\\', '/', $file_path); $file_path = preg_replace('#/+#', '/', $file_path); if (file_exists($file_path)) { @unlink($file_path); } } else { // 2. GCS 파일 삭제 (GCS URI인 경우) $gcs_uri = $consultation['audio_file_path']; if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) { $bucket_name = $matches[1]; $object_name = $matches[2]; $gcs_deleted = deleteFromGCS($bucket_name, $object_name); if (!$gcs_deleted) { error_log('GCS 파일 삭제 실패: ' . $gcs_uri); } } } } // 3. DB에서 삭제 $delete_sql = "DELETE FROM manager_consultations WHERE id = ? AND manager_id = ?"; $delete_stmt = $pdo->prepare($delete_sql); $delete_stmt->execute([$consultation_id, $manager_id]); echo json_encode(['success' => true, 'message' => '녹음 파일이 삭제되었습니다.']); } catch (Exception $e) { error_log('녹음 파일 삭제 오류: ' . $e->getMessage()); echo json_encode(['success' => false, 'message' => '삭제 중 오류가 발생했습니다: ' . $e->getMessage()]); } ?>