first commit
This commit is contained in:
67
sales_manager_scenario/api_handler.php
Normal file
67
sales_manager_scenario/api_handler.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
include '../lib/mydb.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$pdo = db_connect();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$tenantId = $_GET['tenant_id'] ?? 'default_tenant';
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT step_id, checkpoint_index FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND is_checked = 1");
|
||||
$stmt->execute([':tenant_id' => $tenantId]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$data = [];
|
||||
foreach ($rows as $row) {
|
||||
if (!isset($data[$row['step_id']])) {
|
||||
$data[$row['step_id']] = [];
|
||||
}
|
||||
$data[$row['step_id']][] = (int)$row['checkpoint_index'];
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $data]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
elseif ($method === 'POST') {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid input']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$tenantId = $input['tenant_id'] ?? 'default_tenant';
|
||||
$stepId = $input['step_id'];
|
||||
$checkpointIndex = $input['checkpoint_index'];
|
||||
$isChecked = $input['is_checked'] ? 1 : 0;
|
||||
|
||||
try {
|
||||
if ($isChecked) {
|
||||
// Insert or ignore (if already exists)
|
||||
$stmt = $pdo->prepare("INSERT IGNORE INTO manager_scenario_checklist (tenant_id, step_id, checkpoint_index, is_checked) VALUES (:tenant_id, :step_id, :idx, 1)");
|
||||
$stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
|
||||
} else {
|
||||
// Delete record if unchecked
|
||||
$stmt = $pdo->prepare("DELETE FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND checkpoint_index = :idx");
|
||||
$stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
|
||||
}
|
||||
|
||||
// Return updated list for this step
|
||||
$stmt = $pdo->prepare("SELECT checkpoint_index FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND is_checked = 1");
|
||||
$stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId]);
|
||||
$checkedIndices = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
echo json_encode(['success' => true, 'data' => array_map('intval', $checkedIndices)]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
172
sales_manager_scenario/delete_consultation.php
Normal file
172
sales_manager_scenario/delete_consultation.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작
|
||||
ob_start();
|
||||
error_reporting(0);
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// GCS 삭제 함수 (GCP_storage_dev.md 참고)
|
||||
function deleteFromGCS($bucket_name, $object_name, $service_account_path = null) {
|
||||
if (!$service_account_path) {
|
||||
$service_account_path = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
|
||||
}
|
||||
|
||||
if (!file_exists($service_account_path)) {
|
||||
error_log('GCS 삭제 실패: 서비스 계정 파일 없음');
|
||||
return false;
|
||||
}
|
||||
|
||||
$serviceAccount = json_decode(file_get_contents($service_account_path), true);
|
||||
if (!$serviceAccount) {
|
||||
error_log('GCS 삭제 실패: 서비스 계정 JSON 파싱 오류');
|
||||
return false;
|
||||
}
|
||||
|
||||
// OAuth 2.0 토큰 생성
|
||||
$now = time();
|
||||
$jwtHeader = base64_encode(json_encode(['alg' => '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()]);
|
||||
}
|
||||
?>
|
||||
|
||||
113
sales_manager_scenario/download_consultation.php
Normal file
113
sales_manager_scenario/download_consultation.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작
|
||||
ob_start();
|
||||
error_reporting(0);
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// 권한 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
die('접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
// 녹음 파일 ID 확인
|
||||
$consultation_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
$manager_id = $user_id;
|
||||
|
||||
if ($consultation_id <= 0) {
|
||||
header('HTTP/1.0 400 Bad Request');
|
||||
die('잘못된 요청입니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
|
||||
$sql = "SELECT audio_file_path, created_at
|
||||
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 || empty($consultation['audio_file_path'])) {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
die('오디오 파일을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// GCS URI인 경우 처리 불가 (직접 다운로드 불가)
|
||||
if (strpos($consultation['audio_file_path'], 'gs://') === 0) {
|
||||
header('HTTP/1.0 400 Bad Request');
|
||||
die('GCS에 저장된 파일은 직접 다운로드할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 파일 경로 구성
|
||||
$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)) {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
die('오디오 파일이 서버에 존재하지 않습니다.');
|
||||
}
|
||||
|
||||
// 파일 확장자 확인
|
||||
$file_extension = pathinfo($file_path, PATHINFO_EXTENSION) ?: 'webm';
|
||||
$mime_types = [
|
||||
'webm' => 'audio/webm',
|
||||
'wav' => 'audio/wav',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'ogg' => 'audio/ogg',
|
||||
'm4a' => 'audio/mp4'
|
||||
];
|
||||
|
||||
$content_type = isset($mime_types[$file_extension])
|
||||
? $mime_types[$file_extension]
|
||||
: 'audio/webm';
|
||||
|
||||
// 다운로드 파일명 생성
|
||||
$date = date('Ymd_His', strtotime($consultation['created_at']));
|
||||
$download_filename = '상담녹음_' . $date . '.' . $file_extension;
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
$file_size = filesize($file_path);
|
||||
if ($file_size === false || $file_size == 0) {
|
||||
header('HTTP/1.0 500 Internal Server Error');
|
||||
die('파일을 읽을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 헤더 설정
|
||||
header('Content-Type: ' . $content_type);
|
||||
header('Content-Disposition: attachment; filename="' . $download_filename . '"');
|
||||
header('Content-Length: ' . $file_size);
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||||
header('Pragma: public');
|
||||
header('Expires: 0');
|
||||
|
||||
// 파일 출력
|
||||
$handle = @fopen($file_path, 'rb');
|
||||
if ($handle === false) {
|
||||
header('HTTP/1.0 500 Internal Server Error');
|
||||
die('파일을 열 수 없습니다.');
|
||||
}
|
||||
|
||||
while (!feof($handle)) {
|
||||
$chunk = fread($handle, 8192);
|
||||
if ($chunk === false) break;
|
||||
echo $chunk;
|
||||
flush();
|
||||
}
|
||||
fclose($handle);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
header('HTTP/1.0 500 Internal Server Error');
|
||||
die('파일 다운로드 중 오류가 발생했습니다.');
|
||||
}
|
||||
61
sales_manager_scenario/get_consultation_detail.php
Normal file
61
sales_manager_scenario/get_consultation_detail.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||||
error_reporting(0);
|
||||
ob_start();
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 권한 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$consultation_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
$manager_id = $user_id;
|
||||
|
||||
if ($consultation_id <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
|
||||
$sql = "SELECT id, manager_id, step_id, audio_file_path, transcript_text,
|
||||
file_expiry_date, created_at
|
||||
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;
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
$consultation['created_at_formatted'] = date('Y-m-d H:i:s', strtotime($consultation['created_at']));
|
||||
$consultation['is_gcs'] = strpos($consultation['audio_file_path'], 'gs://') === 0;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $consultation
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('조회 오류: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '조회 실패: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
68
sales_manager_scenario/get_consultation_files.php
Normal file
68
sales_manager_scenario/get_consultation_files.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||||
error_reporting(0);
|
||||
ob_start();
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 1. 권한 및 세션 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$manager_id = $user_id;
|
||||
$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 2;
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 20;
|
||||
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
if (!$pdo) {
|
||||
throw new Exception('데이터베이스 연결 실패');
|
||||
}
|
||||
|
||||
// 저장된 첨부파일 목록 조회
|
||||
$sql = "SELECT id, manager_id, step_id, file_paths_json,
|
||||
file_expiry_date, created_at
|
||||
FROM manager_consultation_files
|
||||
WHERE manager_id = ? AND step_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if (!$stmt) {
|
||||
throw new Exception('SQL 준비 실패');
|
||||
}
|
||||
|
||||
$stmt->execute([$manager_id, $step_id, $limit]);
|
||||
$files = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// JSON 파싱 및 날짜 포맷팅
|
||||
foreach ($files as &$file) {
|
||||
$file['files'] = json_decode($file['file_paths_json'], true) ?: [];
|
||||
$file['created_at_formatted'] = date('Y-m-d H:i', strtotime($file['created_at']));
|
||||
$file['file_count'] = count($file['files']);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $files,
|
||||
'count' => count($files)
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('조회 오류: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '조회 실패: ' . $e->getMessage(),
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
67
sales_manager_scenario/get_consultations.php
Normal file
67
sales_manager_scenario/get_consultations.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||||
error_reporting(0);
|
||||
ob_start();
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 1. 권한 및 세션 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$manager_id = $user_id;
|
||||
$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 2;
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 20;
|
||||
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
if (!$pdo) {
|
||||
throw new Exception('데이터베이스 연결 실패');
|
||||
}
|
||||
|
||||
// 저장된 녹음 파일 목록 조회
|
||||
$sql = "SELECT id, manager_id, step_id, audio_file_path, transcript_text,
|
||||
file_expiry_date, created_at
|
||||
FROM manager_consultations
|
||||
WHERE manager_id = ? AND step_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if (!$stmt) {
|
||||
throw new Exception('SQL 준비 실패');
|
||||
}
|
||||
|
||||
$stmt->execute([$manager_id, $step_id, $limit]);
|
||||
$consultations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 날짜 포맷팅
|
||||
foreach ($consultations as &$consultation) {
|
||||
$consultation['created_at_formatted'] = date('Y-m-d H:i', strtotime($consultation['created_at']));
|
||||
$consultation['is_gcs'] = strpos($consultation['audio_file_path'], 'gs://') === 0;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $consultations,
|
||||
'count' => count($consultations)
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('조회 오류: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '조회 실패: ' . $e->getMessage(),
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
1630
sales_manager_scenario/index.php
Normal file
1630
sales_manager_scenario/index.php
Normal file
File diff suppressed because it is too large
Load Diff
218
sales_manager_scenario/save_consultation.php
Normal file
218
sales_manager_scenario/save_consultation.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||||
error_reporting(0);
|
||||
ob_start();
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// GCS 업로드 함수 (GCP_storage_dev.md 참고)
|
||||
function uploadToGCS($file_path, $bucket_name, $object_name, $service_account_path) {
|
||||
if (!file_exists($service_account_path)) {
|
||||
error_log('GCS 업로드 실패: 서비스 계정 파일 없음');
|
||||
return false;
|
||||
}
|
||||
|
||||
$serviceAccount = json_decode(file_get_contents($service_account_path), true);
|
||||
if (!$serviceAccount) {
|
||||
error_log('GCS 업로드 실패: 서비스 계정 JSON 파싱 오류');
|
||||
return false;
|
||||
}
|
||||
|
||||
// OAuth 2.0 토큰 생성
|
||||
$now = time();
|
||||
$jwtHeader = base64_encode(json_encode(['alg' => '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에 파일 업로드
|
||||
$file_content = file_get_contents($file_path);
|
||||
$mime_type = mime_content_type($file_path) ?: 'audio/webm';
|
||||
|
||||
$upload_url = 'https://storage.googleapis.com/upload/storage/v1/b/' .
|
||||
urlencode($bucket_name) . '/o?uploadType=media&name=' .
|
||||
urlencode($object_name);
|
||||
|
||||
$ch = curl_init($upload_url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Content-Type: ' . $mime_type,
|
||||
'Content-Length: ' . strlen($file_content)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code === 200) {
|
||||
return 'gs://' . $bucket_name . '/' . $object_name;
|
||||
} else {
|
||||
error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 1. 권한 및 세션 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$manager_id = $user_id; // session.php에서 user_id를 manager_id로 사용
|
||||
$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/";
|
||||
|
||||
// 2. 파일 업로드 처리
|
||||
if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true);
|
||||
|
||||
if (!isset($_FILES['audio_file'])) {
|
||||
echo json_encode(['success' => false, 'message' => '오디오 파일이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 파일 크기 확인
|
||||
if ($_FILES['audio_file']['size'] == 0) {
|
||||
echo json_encode(['success' => false, 'message' => '오디오 파일이 비어있습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file_name = date('Ymd_His') . "_" . uniqid() . ".webm";
|
||||
$file_path = $upload_dir . $file_name;
|
||||
|
||||
if (!move_uploaded_file($_FILES['audio_file']['tmp_name'], $file_path)) {
|
||||
echo json_encode(['success' => false, 'message' => '파일 저장 실패']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. GCS 업로드 (선택사항 - 파일이 큰 경우)
|
||||
$gcs_uri = null;
|
||||
$file_size = filesize($file_path);
|
||||
$max_local_size = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
if ($file_size > $max_local_size) {
|
||||
// GCS 설정 확인
|
||||
$gcs_config_file = $_SERVER['DOCUMENT_ROOT'] . '/apikey/gcs_config.txt';
|
||||
$bucket_name = null;
|
||||
|
||||
if (file_exists($gcs_config_file)) {
|
||||
$gcs_config = parse_ini_file($gcs_config_file);
|
||||
$bucket_name = isset($gcs_config['bucket_name']) ? $gcs_config['bucket_name'] : null;
|
||||
}
|
||||
|
||||
if ($bucket_name) {
|
||||
$googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
|
||||
$gcs_object_name = 'manager_consultations/' . $manager_id . '/' . basename($file_path);
|
||||
$gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile);
|
||||
|
||||
if ($gcs_uri) {
|
||||
// GCS 업로드 성공 시 로컬 파일 삭제 (선택사항)
|
||||
// @unlink($file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. DB 저장
|
||||
$transcript = isset($_POST['transcript']) ? trim($_POST['transcript']) : '';
|
||||
$step_id = isset($_POST['step_id']) ? intval($_POST['step_id']) : 2;
|
||||
$web_path = "/uploads/manager_consultations/" . $manager_id . "/" . $file_name;
|
||||
$file_path_to_store = $gcs_uri ? $gcs_uri : $web_path;
|
||||
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
if (!$pdo) {
|
||||
throw new Exception('데이터베이스 연결 실패');
|
||||
}
|
||||
|
||||
$expiry_date = date('Y-m-d H:i:s', strtotime('+30 days')); // 30일 보관
|
||||
|
||||
// manager_consultations 테이블에 저장 (테이블이 없으면 생성 필요)
|
||||
$sql = "INSERT INTO manager_consultations
|
||||
(manager_id, step_id, audio_file_path, transcript_text, file_expiry_date, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW())";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if (!$stmt) {
|
||||
throw new Exception('SQL 준비 실패');
|
||||
}
|
||||
|
||||
$executeResult = $stmt->execute([
|
||||
$manager_id,
|
||||
$step_id,
|
||||
$file_path_to_store,
|
||||
$transcript,
|
||||
$expiry_date
|
||||
]);
|
||||
|
||||
if (!$executeResult) {
|
||||
throw new Exception('SQL 실행 실패');
|
||||
}
|
||||
|
||||
$insertId = $pdo->lastInsertId();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '녹음 파일이 저장되었습니다.',
|
||||
'id' => $insertId,
|
||||
'file_path' => $file_path_to_store,
|
||||
'gcs_uri' => $gcs_uri
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('DB 저장 오류: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '데이터베이스 저장 실패: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
234
sales_manager_scenario/upload_consultation_files.php
Normal file
234
sales_manager_scenario/upload_consultation_files.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||||
error_reporting(0);
|
||||
ob_start();
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||||
|
||||
// GCS 업로드 함수 (GCP_storage_dev.md 참고)
|
||||
function uploadToGCS($file_path, $bucket_name, $object_name, $service_account_path) {
|
||||
if (!file_exists($service_account_path)) {
|
||||
error_log('GCS 업로드 실패: 서비스 계정 파일 없음');
|
||||
return false;
|
||||
}
|
||||
|
||||
$serviceAccount = json_decode(file_get_contents($service_account_path), true);
|
||||
if (!$serviceAccount) {
|
||||
error_log('GCS 업로드 실패: 서비스 계정 JSON 파싱 오류');
|
||||
return false;
|
||||
}
|
||||
|
||||
// OAuth 2.0 토큰 생성
|
||||
$now = time();
|
||||
$jwtHeader = base64_encode(json_encode(['alg' => '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에 파일 업로드
|
||||
$file_content = file_get_contents($file_path);
|
||||
$mime_type = mime_content_type($file_path) ?: 'application/octet-stream';
|
||||
|
||||
$upload_url = 'https://storage.googleapis.com/upload/storage/v1/b/' .
|
||||
urlencode($bucket_name) . '/o?uploadType=media&name=' .
|
||||
urlencode($object_name);
|
||||
|
||||
$ch = curl_init($upload_url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Content-Type: ' . $mime_type,
|
||||
'Content-Length: ' . strlen($file_content)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code === 200) {
|
||||
return 'gs://' . $bucket_name . '/' . $object_name;
|
||||
} else {
|
||||
error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 출력 버퍼 비우기
|
||||
ob_clean();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 1. 권한 및 세션 체크
|
||||
if (!isset($user_id) || $level > 5) {
|
||||
echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$manager_id = $user_id;
|
||||
$step_id = isset($_POST['step_id']) ? intval($_POST['step_id']) : 2;
|
||||
$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/files/";
|
||||
|
||||
// 2. 파일 업로드 처리
|
||||
if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true);
|
||||
|
||||
// 업로드된 파일 확인
|
||||
$uploaded_files = [];
|
||||
$file_count = isset($_POST['file_count']) ? intval($_POST['file_count']) : 0;
|
||||
|
||||
for ($i = 0; $i < $file_count; $i++) {
|
||||
$file_key = 'file_' . $i;
|
||||
|
||||
if (!isset($_FILES[$file_key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = $_FILES[$file_key];
|
||||
|
||||
// 파일 크기 확인 (50MB 제한)
|
||||
$max_file_size = 50 * 1024 * 1024;
|
||||
if ($file['size'] > $max_file_size) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '파일 크기가 너무 큽니다. (최대 50MB): ' . $file['name']
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 파일명 생성
|
||||
$original_name = $file['name'];
|
||||
$file_extension = pathinfo($original_name, PATHINFO_EXTENSION);
|
||||
$file_name = date('Ymd_His') . "_" . uniqid() . "." . $file_extension;
|
||||
$file_path = $upload_dir . $file_name;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $file_path)) {
|
||||
echo json_encode(['success' => false, 'message' => '파일 저장 실패: ' . $original_name]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// GCS 업로드 (선택사항 - 큰 파일인 경우)
|
||||
$gcs_uri = null;
|
||||
$file_size = filesize($file_path);
|
||||
$max_local_size = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
if ($file_size > $max_local_size) {
|
||||
$gcs_config_file = $_SERVER['DOCUMENT_ROOT'] . '/apikey/gcs_config.txt';
|
||||
$bucket_name = null;
|
||||
|
||||
if (file_exists($gcs_config_file)) {
|
||||
$gcs_config = parse_ini_file($gcs_config_file);
|
||||
$bucket_name = isset($gcs_config['bucket_name']) ? $gcs_config['bucket_name'] : null;
|
||||
}
|
||||
|
||||
if ($bucket_name) {
|
||||
$googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
|
||||
$gcs_object_name = 'manager_consultations/' . $manager_id . '/files/' . basename($file_path);
|
||||
$gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile);
|
||||
}
|
||||
}
|
||||
|
||||
$web_path = "/uploads/manager_consultations/" . $manager_id . "/files/" . $file_name;
|
||||
$file_path_to_store = $gcs_uri ? $gcs_uri : $web_path;
|
||||
|
||||
$uploaded_files[] = [
|
||||
'original_name' => $original_name,
|
||||
'file_path' => $file_path_to_store,
|
||||
'file_size' => $file_size,
|
||||
'gcs_uri' => $gcs_uri
|
||||
];
|
||||
}
|
||||
|
||||
// 3. DB 저장
|
||||
try {
|
||||
$pdo = db_connect();
|
||||
if (!$pdo) {
|
||||
throw new Exception('데이터베이스 연결 실패');
|
||||
}
|
||||
|
||||
$expiry_date = date('Y-m-d H:i:s', strtotime('+30 days'));
|
||||
$file_paths_json = json_encode($uploaded_files);
|
||||
|
||||
// manager_consultation_files 테이블에 저장
|
||||
$sql = "INSERT INTO manager_consultation_files
|
||||
(manager_id, step_id, file_paths_json, file_expiry_date, created_at)
|
||||
VALUES (?, ?, ?, ?, NOW())";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if (!$stmt) {
|
||||
throw new Exception('SQL 준비 실패');
|
||||
}
|
||||
|
||||
$executeResult = $stmt->execute([
|
||||
$manager_id,
|
||||
$step_id,
|
||||
$file_paths_json,
|
||||
$expiry_date
|
||||
]);
|
||||
|
||||
if (!$executeResult) {
|
||||
throw new Exception('SQL 실행 실패');
|
||||
}
|
||||
|
||||
$insertId = $pdo->lastInsertId();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => count($uploaded_files) . '개 파일이 업로드되었습니다.',
|
||||
'id' => $insertId,
|
||||
'files' => $uploaded_files,
|
||||
'file_count' => count($uploaded_files)
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('DB 저장 오류: ' . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '데이터베이스 저장 실패: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user