first commit

This commit is contained in:
2025-12-17 12:59:26 +09:00
commit 4782df3a50
27 changed files with 8686 additions and 0 deletions

View 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()]);
}
}
?>

View 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()]);
}
?>

View 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('파일 다운로드 중 오류가 발생했습니다.');
}

View 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()
]);
}
?>

View 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' => []
]);
}
?>

View 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' => []
]);
}
?>

File diff suppressed because it is too large Load Diff

View 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()
]);
}
?>

View 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()
]);
}
?>