최초 커밋
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ⭐️ 여기에 추가!
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$family_version = $_POST['family_version'] ?? '';
|
||||
$external_version = $_POST['external_version'] ?? '';
|
||||
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패: ' . $conn->connect_error]);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// FK 에러 방지: products 테이블에 존재하는지 확인
|
||||
$chk = $conn->prepare("SELECT COUNT(*) FROM products WHERE code=?");
|
||||
$chk->bind_param("s", $product_code);
|
||||
$chk->execute();
|
||||
$chk->bind_result($cnt);
|
||||
$chk->fetch();
|
||||
$chk->close();
|
||||
if ($cnt < 1) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '존재하지 않는 제품코드입니다']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 실제 insert/update 쿼리 실행
|
||||
$sql = "INSERT INTO deploy_versions (product_code, family_version, external_version)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version=VALUES(family_version),
|
||||
external_version=VALUES(external_version)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => '쿼리 준비 오류: ' . $conn->error]);
|
||||
exit;
|
||||
}
|
||||
$stmt->bind_param("sss", $product_code, $family_version, $external_version);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['message' => '저장 완료']);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 오류: ' . $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// upload_common.php
|
||||
// 파일 업로드 전용 공통 초기화
|
||||
|
||||
// (1) Descope 세션 불러오기
|
||||
require_once __DIR__ . '/../skin/member/basic/descope_session.php';
|
||||
|
||||
// (2) 현재 로그인 사용자 정보 안전하게 꺼내기
|
||||
$currentUserId = $_SESSION['user']['loginIds'][0] ?? '';
|
||||
$currentUserName = $_SESSION['user']['name'] ?? '알 수 없음';
|
||||
$currentUserEmail= $_SESSION['user']['email'] ?? '';
|
||||
|
||||
// (3) 관리자 여부 플래그
|
||||
$ADMIN_EMAILS = [
|
||||
'kjy0426@hanmaceng.co.kr',
|
||||
'b24014@hanmaceng.co.kr',
|
||||
'b23065@hanmaceng.co.kr',
|
||||
'b23008@baroncs.co.kr',
|
||||
'cjy627@hanmaceng.co.kr',
|
||||
'b23072@hanmaceng.co.kr',
|
||||
'rmsgud1202@hanmaceng.co.kr',
|
||||
'b25023@hanmaceng.co.kr'
|
||||
];
|
||||
$isAdmin = in_array($currentUserId, $ADMIN_EMAILS, true);
|
||||
|
||||
// (4) JSON 응답 시 헤더 자동 세팅 (필요 시)
|
||||
if (!headers_sent()) {
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$pdo = new PDO("mysql:host=localhost;dbname=egbim;charset=utf8mb4","egbim","baron3840!!",[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||
]);
|
||||
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
$swVersion = $_POST['sw_version'] ?? '';
|
||||
|
||||
$st = $pdo->prepare("
|
||||
SELECT COUNT(*) FROM deploy_files
|
||||
WHERE product_code=? AND sw_version=? AND deleted=0
|
||||
");
|
||||
$st->execute([$productCode, $swVersion]);
|
||||
|
||||
echo json_encode(['exists' => $st->fetchColumn() > 0]);
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
// // /egbim/sw_upload/upload_delete.php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
// ini_set('display_errors', 1);
|
||||
// error_reporting(E_ALL);
|
||||
|
||||
// require __DIR__ . '/../vendor/autoload.php'; // AWS SDK
|
||||
|
||||
// use Aws\S3\S3Client;
|
||||
// use Aws\Exception\AwsException;
|
||||
|
||||
// // === DB 연결 ===
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['status' => 'fail', 'message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 파라미터 ===
|
||||
// $ids = $_POST['ids'] ?? [];
|
||||
// if (!is_array($ids) || empty($ids)) {
|
||||
// http_response_code(400);
|
||||
// echo json_encode(['status' => 'fail', 'message' => '삭제할 ID가 없습니다.']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === S3 클라이언트 ===
|
||||
// $bucket = 'baron-software-test'; // 삭제는 test 버킷에서만
|
||||
// $s3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
// 'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// $success = [];
|
||||
// $failed = [];
|
||||
|
||||
// foreach ($ids as $id) {
|
||||
// $id = (int)$id;
|
||||
// $res = $conn->query("SELECT * FROM deploy_files WHERE id={$id} AND deleted=0");
|
||||
// $file = $res->fetch_assoc();
|
||||
// if (!$file) {
|
||||
// $failed[] = ['id' => $id, 'error' => '파일 없음'];
|
||||
// continue;
|
||||
// }
|
||||
// // ❌ 릴리즈된 경우 삭제 금지
|
||||
// if (!empty($file['release_date'])) {
|
||||
// $failed[] = ['error' => '이미 릴리즈된 파일은 삭제할 수 없습니다.'];
|
||||
// continue;
|
||||
// }
|
||||
// $objectKey = $file['filepath'];
|
||||
|
||||
// try {
|
||||
// // 1) S3 객체 삭제
|
||||
// $s3->deleteObject([
|
||||
// 'Bucket' => $bucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 2) DB 플래그 업데이트
|
||||
// $conn->query("UPDATE deploy_files SET deleted=1 WHERE id={$id}");
|
||||
|
||||
// // 3) ✅ 최신 내부 버전 갱신
|
||||
// $productCode = $conn->real_escape_string($file['product_code']);
|
||||
|
||||
// // 삭제 후 남아있는 최신 버전 조회
|
||||
// $verRes = $conn->prepare("
|
||||
// SELECT sw_version
|
||||
// FROM deploy_files
|
||||
// WHERE product_code=? AND deleted=0
|
||||
// ORDER BY
|
||||
// CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
// CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
// CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
// LIMIT 1
|
||||
// ");
|
||||
// $verRes->bind_param("s", $productCode);
|
||||
// $verRes->execute();
|
||||
// $newLatest = $verRes->get_result()->fetch_assoc()['sw_version'] ?? null;
|
||||
|
||||
// if ($newLatest) {
|
||||
// // 남아있는 최신 버전으로 업데이트
|
||||
// $conn->query("
|
||||
// UPDATE deploy_versions_test
|
||||
// SET family_version='{$newLatest}', updated_at=NOW()
|
||||
// WHERE product_code='{$productCode}'
|
||||
// ");
|
||||
// } else {
|
||||
// // 남은 버전이 없으면 초기화
|
||||
// $conn->query("
|
||||
// UPDATE deploy_versions_test
|
||||
// SET family_version='0.0.0', updated_at=NOW()
|
||||
// WHERE product_code='{$productCode}'
|
||||
// ");
|
||||
// }
|
||||
|
||||
// $success[] = $id; // ✅ id 로 반환
|
||||
// } catch (AwsException $e) {
|
||||
// $failed[] = ['id' => $id, 'error' => $e->getAwsErrorMessage()];
|
||||
// }
|
||||
// }
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => empty($failed) ? 'ok' : 'partial',
|
||||
// 'deleted' => $success,
|
||||
// 'failed' => $failed,
|
||||
// ]);
|
||||
?>
|
||||
<?php
|
||||
// /egbim/sw_upload/upload_delete.php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php'; // AWS SDK
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
// === DB 연결 ===
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'fail', 'message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
$swVersion = $_POST['sw_version'] ?? '';
|
||||
|
||||
if (!$productCode || !$swVersion) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'fail', 'message' => '삭제할 대상이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === S3 클라이언트 ===
|
||||
$bucket = 'baron-software-test'; // 삭제는 test 버킷에서만
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
]
|
||||
]);
|
||||
|
||||
$success = [];
|
||||
$failed = [];
|
||||
|
||||
// === 버전 단위 전체 파일 조회 ===
|
||||
$res = $conn->query("
|
||||
SELECT * FROM deploy_files
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
AND sw_version='{$conn->real_escape_string($swVersion)}'
|
||||
AND deleted=0
|
||||
");
|
||||
$files = $res->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
if (!$files) {
|
||||
echo json_encode(['status'=>'fail','message'=>'해당 버전의 파일이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 삭제 처리 루프 ===
|
||||
foreach ($files as $file) {
|
||||
// ❌ 릴리즈된 경우 → 전체 삭제 불가 처리
|
||||
if (!empty($file['release_date'])) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '이미 릴리즈된 파일은 삭제할 수 없습니다.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 릴리즈 안 된 경우만 여기 도달 → 전체 삭제 진행
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
// 1) S3 객체 삭제
|
||||
$s3->deleteObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $file['filepath'],
|
||||
]);
|
||||
|
||||
// 2) DB 플래그 업데이트
|
||||
$conn->query("UPDATE deploy_files SET deleted=1 WHERE id={$file['id']}");
|
||||
|
||||
$success[] = $file['filename'];
|
||||
} catch (AwsException $e) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '파일 삭제 중 오류: '.$e->getAwsErrorMessage()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// === 최신 내부 버전 갱신 ===
|
||||
$verRes = $conn->query("
|
||||
SELECT sw_version
|
||||
FROM deploy_files
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
AND deleted=0
|
||||
ORDER BY
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$newLatest = $verRes->fetch_assoc()['sw_version'] ?? '0.0.0';
|
||||
|
||||
$conn->query("
|
||||
UPDATE deploy_versions_test
|
||||
SET family_version='{$newLatest}', updated_at=NOW()
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
");
|
||||
|
||||
echo json_encode([
|
||||
'status' => empty($failed) ? 'ok' : 'fail',
|
||||
'deleted' => $success,
|
||||
'failed' => $failed,
|
||||
'new_latest' => $newLatest ?? '0.0.0',
|
||||
'message' => empty($failed)
|
||||
? '삭제 완료'
|
||||
: '삭제 실패: 이미 릴리즈된 파일이 있습니다.'
|
||||
]);
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
use Aws\S3\S3Client;
|
||||
|
||||
// 실서비스 시에는 에러 표시를 끄는 것이 좋습니다.
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// 1. 파라미터 받기
|
||||
$filepath = $_GET['path'] ?? '';
|
||||
$filename = $_GET['name'] ?? '';
|
||||
|
||||
if (!$filepath || !$filename) {
|
||||
exit("필수 파라미터(path, name)가 누락되었습니다.");
|
||||
}
|
||||
|
||||
// 2. DB 연결
|
||||
$conn = new mysqli("localhost", "egbim", "baron3840!!", "egbim");
|
||||
|
||||
if ($conn->connect_error) {
|
||||
die("연결 실패: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. DB 조회 (중요 수정 사항)
|
||||
* 동일한 filepath가 여러 개일 경우:
|
||||
* 1순위: release_date가 있는 것 우선 (DESC)
|
||||
* 2순위: 가장 최근에 업로드된 것 우선 (upload_date DESC)
|
||||
*/
|
||||
$sql = "SELECT release_date FROM deploy_files
|
||||
WHERE filepath = ?
|
||||
ORDER BY (release_date IS NULL), release_date DESC, upload_date DESC
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("s", $filepath);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($release_date);
|
||||
$fetch_success = $stmt->fetch();
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
|
||||
// DB에 해당 파일 경로 자체가 없는 경우
|
||||
if (!$fetch_success) {
|
||||
exit("DB에서 파일 정보를 찾을 수 없습니다. 경로: " . htmlspecialchars($filepath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 4. 버킷 결정 로직
|
||||
* 데이터가 존재하고, NULL/공백/0000-00-00이 아닐 때만 release 버킷 사용
|
||||
*/
|
||||
$is_released = (!empty($release_date) && $release_date !== '0000-00-00');
|
||||
$bucket = $is_released ? 'baron-software-release' : 'baron-software-test';
|
||||
|
||||
// 5. S3 Client 설정
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint'=> 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
],
|
||||
'use_path_style_endpoint' => true
|
||||
]);
|
||||
|
||||
try {
|
||||
// 6. Presigned URL 생성 (10분 유효)
|
||||
$cmd = $s3->getCommand('GetObject', [
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $filepath,
|
||||
'ResponseContentDisposition' => 'attachment; filename="' . rawurlencode($filename) . '"'
|
||||
]);
|
||||
|
||||
$request = $s3->createPresignedRequest($cmd, '+10 minutes');
|
||||
|
||||
// 7. R2 직접 다운로드 주소로 리다이렉트
|
||||
header("Location: " . (string)$request->getUri());
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
exit("다운로드 링크 생성 실패: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// // require_once __DIR__ . '/upload_common.php';
|
||||
// // require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/admin_guard.php';
|
||||
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 선택된 제품코드 파라미터 ===
|
||||
// $product_code = $_GET['product_code'] ?? '';
|
||||
|
||||
// // TODO: 권한 부여는 추후 적용
|
||||
// // $user_role = $_SESSION['user']['role'] ?? 'user'; // 예: admin, manager, user
|
||||
// // if ($user_role !== 'admin') {
|
||||
// // // 권한 체크 로직 들어갈 자리
|
||||
// // }
|
||||
|
||||
// $sql = "SELECT id, product_code, sw_version, version_type,
|
||||
// filename, filepath, filetype,
|
||||
// filesize, uploader,
|
||||
// DATE_FORMAT(upload_date, '%Y-%m-%d %H:%i:%s') as upload_date,
|
||||
// DATE_FORMAT(test_done_date, '%Y-%m-%d %H:%i:%s') as test_done_date,
|
||||
// tester,
|
||||
// DATE_FORMAT(release_date, '%Y-%m-%d %H:%i:%s') as release_date,
|
||||
// releaser,
|
||||
// deleted
|
||||
// FROM deploy_files
|
||||
// WHERE 1=1";
|
||||
|
||||
// // 제품코드가 지정된 경우만 필터
|
||||
// if ($product_code !== '') {
|
||||
// $sql .= " AND product_code = '". $conn->real_escape_string($product_code) ."'";
|
||||
// }
|
||||
|
||||
// $sql .= " ORDER BY id DESC";
|
||||
|
||||
// $res = $conn->query($sql);
|
||||
// $files = [];
|
||||
// while ($row = $res->fetch_assoc()) {
|
||||
// $files[] = $row;
|
||||
// }
|
||||
|
||||
// echo json_encode(['files' => $files], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// $conn->close();
|
||||
?>
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// ✅ 1) Descope 세션 복원 (JWT → $_SESSION['user'])
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
|
||||
// ✅ 2) 관리자 권한 체크
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/upload_admin_guard.php';
|
||||
// ✅ 관리자만 접근 가능. 일반 사용자는 403 Forbidden
|
||||
|
||||
$host = 'localhost';
|
||||
$user = 'egbim';
|
||||
$pass = 'baron3840!!';
|
||||
$db = 'egbim';
|
||||
|
||||
$conn = new mysqli($host, $user, $pass, $db);
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status'=>'fail','message'=>'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$product_code = $_GET['product_code'] ?? '';
|
||||
$show_deleted = isset($_GET['show_deleted']) ? (int)$_GET['show_deleted'] : 0;
|
||||
|
||||
if (!$product_code) {
|
||||
echo json_encode(['status'=>'fail','message'=>'제품코드 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 삭제 여부 조건 ===
|
||||
$whereDeleted = $show_deleted ? "1=1" : "deleted=0";
|
||||
|
||||
// === 쿼리 ===
|
||||
$sql = "
|
||||
SELECT
|
||||
id, product_code, sw_version, filename, filetype, filesize, filepath,
|
||||
DATE_FORMAT(upload_date, '%Y-%m-%d %H:%i:%s') AS upload_date,
|
||||
uploader,
|
||||
tester,
|
||||
DATE_FORMAT(test_done_date, '%Y-%m-%d %H:%i:%s') AS test_done_date,
|
||||
releaser,
|
||||
DATE_FORMAT(release_date, '%Y-%m-%d %H:%i:%s') AS release_date,
|
||||
deleted
|
||||
FROM deploy_files
|
||||
WHERE product_code = ?
|
||||
AND $whereDeleted
|
||||
ORDER BY sw_version DESC, upload_date ASC
|
||||
";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("s", $product_code);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
|
||||
$files = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$files[] = $row;
|
||||
}
|
||||
|
||||
// ✅ 현재 로그인 관리자 정보 포함
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'files' => $files,
|
||||
'current_user' => [
|
||||
'loginId' => $_SESSION['user']['loginIds'][0] ?? '',
|
||||
'name' => get_upload_admin_display_name(
|
||||
$_SESSION['user']['loginIds'][0] ?? '',
|
||||
$_SESSION['user']['name'] ?? '알 수 없음'
|
||||
)
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -0,0 +1,633 @@
|
||||
<?php
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/upload_admin_guard.php';
|
||||
|
||||
// 로그인 안 되었거나 관리자 아니면 바로 로그인 화면으로
|
||||
if (empty($_SESSION['user']['userId']) || !is_upload_admin()) {
|
||||
header("Location: /egbim/index.php?popup=login");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>배포 파일 관리 시스템</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 p-6">
|
||||
<div class="max-w-screen-xl mx-auto">
|
||||
<div class="flex items-center mb-4">
|
||||
<svg class="w-7 h-7 text-blue-600 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5-5m0 0l5 5m-5-5v12" />
|
||||
</svg>
|
||||
<span class="text-xl font-bold mr-4">한맥 가족사 S/W 업로드</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded shadow mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="product-select" class="font-bold">제품명(제품코드):</label>
|
||||
<select id="product-select" class="p-2 border border-gray-300 rounded">
|
||||
<option value="1">EG-BIM</option>
|
||||
<option value="2">TOVA</option>
|
||||
<option value="3">GAIA</option>
|
||||
<option value="4">EG-BIM-MODELER</option>
|
||||
<option value="999" selected>TEST</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<span class="text-sm">내부 배포 버전(최신):</span>
|
||||
<span id="latest-family" class="text-blue-600 font-semibold">0.0.0</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="text-sm">외부 배포 버전:</span>
|
||||
<select id="external-version" class="p-1 border rounded">
|
||||
<option value="">사용안함</option>
|
||||
</select>
|
||||
<button id="save-version" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="show-deleted" class="mr-1">
|
||||
<label for="show-deleted" class="text-sm">삭제된 버전 표시</label>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<button id="btn-new" class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">+ 신규 버전</button>
|
||||
<button class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600" id="btn-delete">파일삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-x-auto max-h-[70vh] overflow-y-auto">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-gray-200 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="px-2 py-2 text-center w-[4%]"></th>
|
||||
<th class="px-2 py-2 text-center w-[7%] whitespace-nowrap">버전</th>
|
||||
<th class="px-2 py-2 text-center w-[20%] whitespace-nowrap">파일명</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">파일타입</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">크기</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">업로드일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">업로드수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">테스트완료</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">테스트수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">릴리즈일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">릴리즈수행자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-list-1" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-2" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-3" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-4" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-999" class="file-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white p-8 rounded shadow-lg w-full max-w-lg relative">
|
||||
<h2 class="text-xl font-bold mb-6 text-center">신규 버전 업로드</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center">
|
||||
<label class="w-28">S/W 버전 :</label>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw1" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw2" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw3" min="0" max="999" required>
|
||||
<span id="version-type-text" class="ml-3 font-bold text-gray-600">Major</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">설치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="setup-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="setup-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="setup-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">패치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="patch-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="patch-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="patch-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">릴리즈노트(MD):</label>
|
||||
<input type="file" class="hidden" id="release-file" accept=".md">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="release-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="release-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">정보파일(ZIP):</label>
|
||||
<input type="file" class="hidden" id="info-file">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="info-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="info-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-2 mt-6">
|
||||
<button id="upload-submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">업로드</button>
|
||||
<button id="upload-cancel" class="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 팝업 열기/닫기
|
||||
$('#btn-new').on('click', ()=>$('#upload-modal').removeClass('hidden'));
|
||||
$('#upload-cancel').on('click', ()=>$('#upload-modal').addClass('hidden'));
|
||||
$('#upload-modal').on('click', function(e){ if(e.target===this) $(this).addClass('hidden'); });
|
||||
|
||||
// 파일 버튼
|
||||
$('#setup-add').on('click', ()=>$('#setup-file').click());
|
||||
$('#patch-add').on('click', ()=>$('#patch-file').click());
|
||||
$('#info-add').on('click', ()=>$('#info-file').click());
|
||||
$('#release-add').on('click', ()=>$('#release-file').click());
|
||||
|
||||
// 파일명 표시
|
||||
$('#setup-file').on('change', function(){ $('#setup-filename').val(this.files[0]?.name || ''); });
|
||||
$('#patch-file').on('change', function(){ $('#patch-filename').val(this.files[0]?.name || ''); });
|
||||
$('#info-file').on('change', function(){ $('#info-filename').val(this.files[0]?.name || ''); });
|
||||
$('#release-file').on('change', function(){ $('#release-filename').val(this.files[0]?.name || ''); });
|
||||
|
||||
function getCurrentUserName() {
|
||||
return window.currentUserName || "알 수 없음";
|
||||
}
|
||||
|
||||
// 업로드 실행
|
||||
$('#upload-submit').on('click', async function() {
|
||||
const productCode = $('#product-select').val();
|
||||
const swVersion = [$('#sw1').val(), $('#sw2').val(), $('#sw3').val()].join('.');
|
||||
const versionType = $('#version-type-text').text().toLowerCase();
|
||||
const sw1 = $('#sw1').val();
|
||||
const sw2 = $('#sw2').val();
|
||||
const sw3 = $('#sw3').val();
|
||||
|
||||
if (!sw1 || !sw2 || !sw3) {
|
||||
alert("S/W 버전을 모두 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const setupFile = $('#setup-file')[0].files[0];
|
||||
const patchFile = $('#patch-file')[0].files[0];
|
||||
const infoFile = $('#info-file')[0].files[0];
|
||||
const releaseFile = $('#release-file')[0].files[0];
|
||||
|
||||
if (!setupFile) {
|
||||
alert("설치파일(EXE)은 반드시 업로드해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let checkRes = await $.post("/egbim/sw_upload/upload_check_version.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion
|
||||
});
|
||||
|
||||
if (checkRes.exists) {
|
||||
const proceed = confirm(`기존에 업로드된 [${swVersion}] 버전이 존재합니다.\n파일을 덮어쓰겠습니까?`);
|
||||
if (!proceed) return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("버전 확인 오류:", err);
|
||||
alert("버전 확인 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 파일 규칙 이름 롤백 (텍스트 변환 제거, 원래대로 숫자 코드 사용)
|
||||
const rename = (type, ext) => {
|
||||
// 제품 코드와 제품명 매핑 (DB/서버 폴더 구조와 일치해야 함)
|
||||
const productMap = {
|
||||
"1": "eg-bim",
|
||||
"2": "tova",
|
||||
"3": "gaia",
|
||||
"4": "eg-bim-modeler", // 수정된 부분: EG-BIM-MODELER 파일명 규칙 매핑 추가
|
||||
"999": "test"
|
||||
};
|
||||
|
||||
const prodName = productMap[productCode] || productCode; // 매핑 없으면 코드 그대로 사용
|
||||
const ver = swVersion.toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "setup": return `setup_${prodName}_${ver}${ext}`;
|
||||
case "patch": return `patch_${prodName}_${ver}${ext}`;
|
||||
case "info": return `updateinfo_${prodName}_${ver}${ext}`;
|
||||
case "md": return `releasenote_${prodName}_${ver}${ext}`;
|
||||
}
|
||||
};
|
||||
|
||||
const files = [];
|
||||
if (setupFile) files.push({ orig: setupFile, type:"setup", ext:".exe" });
|
||||
if (patchFile) files.push({ orig: patchFile, type:"patch", ext:".exe" });
|
||||
if (infoFile) files.push({ orig: infoFile, type:"info", ext:"." + infoFile.name.split(".").pop() });
|
||||
if (releaseFile) files.push({ orig: releaseFile, type:"md", ext:".md" });
|
||||
|
||||
try {
|
||||
for (let f of files) {
|
||||
const renamed = rename(f.type, f.ext);
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("product_code", productCode);
|
||||
formData.append("sw_version", swVersion);
|
||||
formData.append("filename", renamed);
|
||||
formData.append("content_type", f.orig.type || "application/octet-stream");
|
||||
|
||||
let presignRes = await fetch("/egbim/sw_upload/upload_presign.php", { method:"POST", body:formData });
|
||||
let presignData = await presignRes.json();
|
||||
|
||||
let putRes = await fetch(presignData.url, {
|
||||
method:"PUT",
|
||||
headers:{ "Content-Type": f.orig.type || "application/octet-stream" },
|
||||
body:f.orig
|
||||
});
|
||||
if (!putRes.ok) throw new Error(renamed+" 업로드 실패");
|
||||
|
||||
f.path = presignData.key;
|
||||
f.name = renamed;
|
||||
f.size = f.orig.size;
|
||||
f.filetype = f.type;
|
||||
}
|
||||
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_popup.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
version_type: versionType,
|
||||
uploader: userName,
|
||||
files: JSON.stringify(files.map(f=>({
|
||||
name: f.name,
|
||||
original: f.orig.name,
|
||||
path: f.path,
|
||||
type: f.filetype,
|
||||
size: f.size
|
||||
})))
|
||||
}, function(res){
|
||||
if(res.group){
|
||||
loadFileList(productCode);
|
||||
alert("업로드 및 DB 저장 성공!");
|
||||
}
|
||||
}, "json");
|
||||
|
||||
$('#upload-modal').addClass('hidden');
|
||||
|
||||
} catch(err){
|
||||
alert("업로드 중 오류: "+err.message);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
const productCode = $('#product-select').val();
|
||||
loadFileList(productCode);
|
||||
loadVersion(productCode);
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
});
|
||||
|
||||
function loadFileList(productCode){
|
||||
const showDeleted = $("#show-deleted").is(":checked") ? 1 : 0;
|
||||
|
||||
$.ajax({
|
||||
url: "/egbim/sw_upload/upload_list.php",
|
||||
type: "GET",
|
||||
data: { product_code: productCode, show_deleted: showDeleted },
|
||||
dataType: "json",
|
||||
success: function(res){
|
||||
const $tbody = $("#file-list-" + productCode);
|
||||
$tbody.empty();
|
||||
|
||||
if(res.status !== "ok"){
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-red-500">조회 실패: ${res.message}</td></tr>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(res.files && res.files.length > 0){
|
||||
renderFilesFromDB(res.files, $tbody);
|
||||
} else {
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-gray-500">데이터가 없습니다.</td></tr>`);
|
||||
}
|
||||
},
|
||||
error: function(xhr){
|
||||
alert("리스트 조회 중 오류가 발생했습니다. (code: "+xhr.status+")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", "#show-deleted", function(){
|
||||
const productCode = $("#product-select").val();
|
||||
loadFileList(productCode);
|
||||
});
|
||||
|
||||
function renderFilesFromDB(files, $tbody){
|
||||
let grouped = {};
|
||||
files.forEach(f=>{
|
||||
let key = f.sw_version+"_"+f.upload_date;
|
||||
if(!grouped[key]) grouped[key] = {meta:f, files:[]};
|
||||
grouped[key].files.push(f);
|
||||
});
|
||||
|
||||
Object.values(grouped).forEach(g=>{
|
||||
let rowspan = g.files.length;
|
||||
g.files.forEach((f, idx)=>{
|
||||
// ✅ 직접 다운로드 API 주소 생성 (filepath와 filename을 인자로 전달)
|
||||
const downloadUrl = `/egbim/sw_upload/upload_download.php?path=${encodeURIComponent(f.filepath)}&name=${encodeURIComponent(f.filename)}`;
|
||||
|
||||
$tbody.append(`
|
||||
<tr class="border-b">
|
||||
${idx===0 ? `
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}" rowspan="${rowspan}">
|
||||
${f.deleted==1 ? '' : `<input type="checkbox" value="${f.product_code}|${f.sw_version}"/>`}
|
||||
</td>
|
||||
<td class="px-4 py-2 font-bold text-blue-600 text-center" rowspan="${rowspan}">
|
||||
${f.sw_version}
|
||||
</td>
|
||||
` : ''}
|
||||
|
||||
<td class="px-4 py-2 ${f.deleted==1?'bg-red-100':''}">
|
||||
<a href="${downloadUrl}" class="text-blue-600 hover:text-blue-800 hover:underline flex items-center" target="_blank">
|
||||
<i class="fa-solid fa-file-arrow-down mr-2 opacity-70"></i>
|
||||
${f.filename}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">${f.filetype}</td>
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">
|
||||
${f.filetype==="md"
|
||||
? (Math.round(f.filesize/1024*100)/100)+"KB"
|
||||
: (Math.round(f.filesize/1024/1024*100)/100)+"MB"}
|
||||
</td>
|
||||
|
||||
${idx===0?`
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.upload_date}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.uploader}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.test_done_date
|
||||
? f.test_done_date
|
||||
: `<button class="bg-green-500 text-white px-2 py-1 rounded btn-testdone"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">완료확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.tester || ''}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.release_date
|
||||
? f.release_date
|
||||
: `<button class="bg-blue-500 text-white px-2 py-1 rounded btn-release"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">배포확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.releaser || ''}</td>
|
||||
`:''}
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
$(".file-list").addClass("hidden");
|
||||
$("#file-list-" + newCode).removeClass("hidden");
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
|
||||
function updateVersionType() {
|
||||
const major = parseInt($('#sw1').val() || 0, 10);
|
||||
const minor = parseInt($('#sw2').val() || 0, 10);
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
|
||||
let typeText = 'MAJOR';
|
||||
if (patch > 0) {
|
||||
typeText = 'PATCH';
|
||||
} else if (minor > 0) {
|
||||
typeText = 'MINOR';
|
||||
} else if (major > 0) {
|
||||
typeText = 'MAJOR';
|
||||
}
|
||||
|
||||
$('#version-type-text').text(typeText);
|
||||
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
}
|
||||
|
||||
$('#sw1, #sw2, #sw3').on('input', updateVersionType);
|
||||
updateVersionType();
|
||||
|
||||
$(document).on('click', '.btn-testdone', function(){
|
||||
const productCode = $(this).data('product');
|
||||
const swVersion = $(this).data('version');
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_testdone.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
tester: userName
|
||||
}, function(res){
|
||||
if(res.test_done_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(7).text(res.test_done_date);
|
||||
$(this).find("td").eq(8).text(res.tester);
|
||||
}
|
||||
});
|
||||
alert("테스트 완료되었습니다.");
|
||||
loadFileList(productCode);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-release", function(){
|
||||
const productCode = $(this).data("product");
|
||||
const swVersion = $(this).data("version");
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
// ✅ 1. 사용자에게 제시할 확인 문구 설정
|
||||
const confirmText = "배포 완료 요청";
|
||||
|
||||
// ✅ 2. 입력창(prompt) 띄우기
|
||||
const userInput = prompt(`[버전 ${swVersion}] 릴리즈 서버로 이동하시겠습니까?\n\n진행하시려면 하단에 "${confirmText}" 문구를 정확히 입력해주세요.`);
|
||||
|
||||
// ✅ 3. 검증 로직
|
||||
if (userInput === null) return; // '취소'를 누른 경우
|
||||
|
||||
if (userInput.trim() !== confirmText) {
|
||||
alert(`입력한 문구가 일치하지 않습니다.\n(입력: ${userInput})`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 4. 문구가 일치할 경우에만 기존 서버 통신(AJAX) 실행
|
||||
$.post("/egbim/sw_upload/upload_release.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
releaser: userName
|
||||
}, function(res){
|
||||
if(res.release_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(9).text(res.release_date);
|
||||
$(this).find("td").eq(10).text(res.releaser);
|
||||
}
|
||||
});
|
||||
alert(`[버전 ${swVersion}] 배포가 성공적으로 확정되었습니다.`);
|
||||
loadFileList(productCode);
|
||||
} else {
|
||||
alert("실패: " + (res.message || "배포 확정 처리 중 오류 발생"));
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", "#btn-delete", function () {
|
||||
const checked = $("input[type=checkbox]:checked").map(function () { return $(this).val(); }).get();
|
||||
if (checked.length === 0) {
|
||||
alert("삭제할 버전을 선택하세요.");
|
||||
return;
|
||||
}
|
||||
if (!confirm("선택한 버전 전체 파일을 삭제하시겠습니까?")) return;
|
||||
|
||||
checked.forEach(item => {
|
||||
const [product, version] = item.split("|");
|
||||
$.post("/egbim/sw_upload/upload_delete.php", {
|
||||
product_code: product,
|
||||
sw_version: version
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert(`[${version}] 삭제 완료!`);
|
||||
loadFileList(product);
|
||||
} else {
|
||||
if (res.failed && res.failed.length > 0) {
|
||||
let errors = res.failed.map(f => `${f.file}: ${f.error}`).join("\n");
|
||||
alert("삭제 실패:\n" + errors);
|
||||
} else {
|
||||
alert("삭제 실패: " + (res.message || "알 수 없는 오류"));
|
||||
}
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 저장/변경 버튼 클릭 이벤트 수정
|
||||
$(document).on("click", "#save-version", function() {
|
||||
const productCode = $("#product-select").val();
|
||||
const version = $("#external-version").val(); // "사용안함" 선택 시 "" 값이 전달됨
|
||||
|
||||
const msg = (version && version !== "")
|
||||
? `외부 배포를 [ ${version} ] 버전으로 변경하시겠습니까?`
|
||||
: `외부 배포를 '사용안함'으로 설정하시겠습니까?`;
|
||||
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
$.post("/egbim/sw_upload/upload_version_update.php", {
|
||||
product_code: productCode,
|
||||
version: version // 백엔드에서 ""를 받으면 내부 버전과 동일하게 맞춰 "사용안함" 처리함
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert("배포 버전 설정이 완료되었습니다.");
|
||||
loadVersion(productCode); // UI 새로고침
|
||||
} else {
|
||||
alert("설정 실패: " + res.message);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
// ✅ 외부 배포 버전 정보 및 선택 목록 로드 함수 수정
|
||||
function loadVersion(productCode) {
|
||||
$.getJSON("/egbim/sw_upload/upload_version_get.php", { product_code: productCode }, function(res) {
|
||||
if (res.status === "ok") {
|
||||
// ✅ 이 코드가 있어야 "0.0.0" 대신 "1.2.0" 등의 실제 버전이 화면에 보입니다.
|
||||
$("#latest-family").text(res.family_version);
|
||||
|
||||
const $sel = $("#external-version");
|
||||
$sel.empty();
|
||||
|
||||
// 목록 그리기
|
||||
if (res.confirmed_versions) {
|
||||
res.confirmed_versions.forEach(v => {
|
||||
const label = (v === "") ? "사용안함" : v;
|
||||
$sel.append(`<option value="${v}">${label}</option>`);
|
||||
});
|
||||
}
|
||||
|
||||
$sel.val(res.external_version);
|
||||
|
||||
if (res.external_version !== "") {
|
||||
$("#save-version").text("변경").removeClass("bg-blue-500").addClass("bg-yellow-500");
|
||||
} else {
|
||||
$("#save-version").text("저장").removeClass("bg-yellow-500").addClass("bg-blue-500");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindRangeCorrection(selector) {
|
||||
$(selector).on("input", function() {
|
||||
let val = parseInt($(this).val(), 10);
|
||||
let min = parseInt($(this).attr("min"), 10) || 0;
|
||||
let max = parseInt($(this).attr("max"), 10);
|
||||
|
||||
if (isNaN(val)) {
|
||||
$(this).val("");
|
||||
} else if (val < min) {
|
||||
$(this).val(min);
|
||||
} else if (val > max) {
|
||||
$(this).val(max);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindRangeCorrection("#sw1, #sw2, #sw3");
|
||||
|
||||
$('#btn-new').on('click', ()=>{
|
||||
$('#setup-file, #patch-file, #release-file, #info-file').val('');
|
||||
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
|
||||
|
||||
const latestVer = $('#latest-family').text().trim();
|
||||
if (latestVer) {
|
||||
const parts = latestVer.split('.');
|
||||
$('#sw1').val(parts[0] || 0);
|
||||
$('#sw2').val(parts[1] || 0);
|
||||
$('#sw3').val(parts[2] || 0);
|
||||
}
|
||||
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
|
||||
updateVersionType();
|
||||
$('#upload-modal').removeClass('hidden');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/upload_admin_guard.php';
|
||||
|
||||
// 로그인 안 되었거나 관리자 아니면 바로 로그인 화면으로
|
||||
if (empty($_SESSION['user']['userId']) || !is_upload_admin()) {
|
||||
header("Location: /egbim/index.php?popup=login");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>배포 파일 관리 시스템</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 p-6">
|
||||
<div class="max-w-screen-xl mx-auto">
|
||||
<div class="flex items-center mb-4">
|
||||
<svg class="w-7 h-7 text-blue-600 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5-5m0 0l5 5m-5-5v12" />
|
||||
</svg>
|
||||
<span class="text-xl font-bold mr-4">한맥 가족사 S/W 업로드</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded shadow mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="product-select" class="font-bold">제품명(제품코드):</label>
|
||||
<select id="product-select" class="p-2 border border-gray-300 rounded">
|
||||
<option value="1">EG-BIM</option>
|
||||
<option value="2">TOVA</option>
|
||||
<option value="3">GAIA</option>
|
||||
<option value="4">EG-BIM-MODELER</option>
|
||||
<option value="5">EG-BIM_VIEWER</option>
|
||||
<option value="999" selected>TEST</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<span class="text-sm">내부 배포 버전(최신):</span>
|
||||
<span id="latest-family" class="text-blue-600 font-semibold">0.0.0</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="text-sm">외부 배포 버전:</span>
|
||||
<select id="external-version" class="p-1 border rounded">
|
||||
<option value="">사용안함</option>
|
||||
</select>
|
||||
<button id="save-version" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="show-deleted" class="mr-1">
|
||||
<label for="show-deleted" class="text-sm">삭제된 버전 표시</label>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<button id="btn-new" class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">+ 신규 버전</button>
|
||||
<button class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600" id="btn-delete">파일삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-x-auto max-h-[70vh] overflow-y-auto">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-gray-200 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="px-2 py-2 text-center w-[4%]"></th>
|
||||
<th class="px-2 py-2 text-center w-[7%] whitespace-nowrap">버전</th>
|
||||
<th class="px-2 py-2 text-center w-[20%] whitespace-nowrap">파일명</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">파일타입</th>
|
||||
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">크기</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">업로드일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">업로드수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">테스트완료</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">테스트수행자</th>
|
||||
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">릴리즈일</th>
|
||||
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">릴리즈수행자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-list-1" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-2" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-3" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-4" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-5" class="file-list hidden"></tbody>
|
||||
<tbody id="file-list-999" class="file-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white p-8 rounded shadow-lg w-full max-w-lg relative">
|
||||
<h2 class="text-xl font-bold mb-6 text-center">신규 버전 업로드</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center">
|
||||
<label class="w-28">S/W 버전 :</label>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw1" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw2" min="0" max="99" required>
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw3" min="0" max="999" required>
|
||||
<span id="version-type-text" class="ml-3 font-bold text-gray-600">Major</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">설치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="setup-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="setup-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="setup-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">패치파일(EXE):</label>
|
||||
<input type="file" class="hidden" id="patch-file" accept=".exe">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="patch-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="patch-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">릴리즈노트(MD):</label>
|
||||
<input type="file" class="hidden" id="release-file" accept=".md">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="release-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="release-add">추가</button>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-36">정보파일(ZIP):</label>
|
||||
<input type="file" class="hidden" id="info-file">
|
||||
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="info-filename" readonly>
|
||||
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="info-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-2 mt-6">
|
||||
<button id="upload-submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">업로드</button>
|
||||
<button id="upload-cancel" class="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 팝업 열기/닫기
|
||||
$('#btn-new').on('click', ()=>$('#upload-modal').removeClass('hidden'));
|
||||
$('#upload-cancel').on('click', ()=>$('#upload-modal').addClass('hidden'));
|
||||
$('#upload-modal').on('click', function(e){ if(e.target===this) $(this).addClass('hidden'); });
|
||||
|
||||
// 파일 버튼
|
||||
$('#setup-add').on('click', ()=>$('#setup-file').click());
|
||||
$('#patch-add').on('click', ()=>$('#patch-file').click());
|
||||
$('#info-add').on('click', ()=>$('#info-file').click());
|
||||
$('#release-add').on('click', ()=>$('#release-file').click());
|
||||
|
||||
// 파일명 표시
|
||||
$('#setup-file').on('change', function(){ $('#setup-filename').val(this.files[0]?.name || ''); });
|
||||
$('#patch-file').on('change', function(){ $('#patch-filename').val(this.files[0]?.name || ''); });
|
||||
$('#info-file').on('change', function(){ $('#info-filename').val(this.files[0]?.name || ''); });
|
||||
$('#release-file').on('change', function(){ $('#release-filename').val(this.files[0]?.name || ''); });
|
||||
|
||||
function getCurrentUserName() {
|
||||
return window.currentUserName || "알 수 없음";
|
||||
}
|
||||
|
||||
// 업로드 실행
|
||||
$('#upload-submit').on('click', async function() {
|
||||
const productCode = $('#product-select').val();
|
||||
const swVersion = [$('#sw1').val(), $('#sw2').val(), $('#sw3').val()].join('.');
|
||||
const versionType = $('#version-type-text').text().toLowerCase();
|
||||
const sw1 = $('#sw1').val();
|
||||
const sw2 = $('#sw2').val();
|
||||
const sw3 = $('#sw3').val();
|
||||
|
||||
if (!sw1 || !sw2 || !sw3) {
|
||||
alert("S/W 버전을 모두 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const setupFile = $('#setup-file')[0].files[0];
|
||||
const patchFile = $('#patch-file')[0].files[0];
|
||||
const infoFile = $('#info-file')[0].files[0];
|
||||
const releaseFile = $('#release-file')[0].files[0];
|
||||
|
||||
if (!setupFile) {
|
||||
alert("설치파일(EXE)은 반드시 업로드해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let checkRes = await $.post("/egbim/sw_upload/upload_check_version.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion
|
||||
});
|
||||
|
||||
if (checkRes.exists) {
|
||||
const proceed = confirm(`기존에 업로드된 [${swVersion}] 버전이 존재합니다.\n파일을 덮어쓰겠습니까?`);
|
||||
if (!proceed) return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("버전 확인 오류:", err);
|
||||
alert("버전 확인 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 파일 규칙 이름 롤백 (텍스트 변환 제거, 원래대로 숫자 코드 사용)
|
||||
const rename = (type, ext) => {
|
||||
// 제품 코드와 제품명 매핑 (DB/서버 폴더 구조와 일치해야 함)
|
||||
const productMap = {
|
||||
"1": "eg-bim",
|
||||
"2": "tova",
|
||||
"3": "gaia",
|
||||
"4": "eg-bim-modeler", // 수정된 부분: EG-BIM-MODELER 파일명 규칙 매핑 추가
|
||||
"5": "eg-bim-viewer",
|
||||
"999": "test"
|
||||
};
|
||||
|
||||
const prodName = productMap[productCode] || productCode; // 매핑 없으면 코드 그대로 사용
|
||||
const ver = swVersion.toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "setup": return `setup_${prodName}_${ver}${ext}`;
|
||||
case "patch": return `patch_${prodName}_${ver}${ext}`;
|
||||
case "info": return `updateinfo_${prodName}_${ver}${ext}`;
|
||||
case "md": return `releasenote_${prodName}_${ver}${ext}`;
|
||||
}
|
||||
};
|
||||
|
||||
const files = [];
|
||||
if (setupFile) files.push({ orig: setupFile, type:"setup", ext:".exe" });
|
||||
if (patchFile) files.push({ orig: patchFile, type:"patch", ext:".exe" });
|
||||
if (infoFile) files.push({ orig: infoFile, type:"info", ext:"." + infoFile.name.split(".").pop() });
|
||||
if (releaseFile) files.push({ orig: releaseFile, type:"md", ext:".md" });
|
||||
|
||||
try {
|
||||
for (let f of files) {
|
||||
const renamed = rename(f.type, f.ext);
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("product_code", productCode);
|
||||
formData.append("sw_version", swVersion);
|
||||
formData.append("filename", renamed);
|
||||
formData.append("content_type", f.orig.type || "application/octet-stream");
|
||||
|
||||
let presignRes = await fetch("/egbim/sw_upload/upload_presign.php", { method:"POST", body:formData });
|
||||
let presignData = await presignRes.json();
|
||||
|
||||
let putRes = await fetch(presignData.url, {
|
||||
method:"PUT",
|
||||
headers:{ "Content-Type": f.orig.type || "application/octet-stream" },
|
||||
body:f.orig
|
||||
});
|
||||
if (!putRes.ok) throw new Error(renamed+" 업로드 실패");
|
||||
|
||||
f.path = presignData.key;
|
||||
f.name = renamed;
|
||||
f.size = f.orig.size;
|
||||
f.filetype = f.type;
|
||||
}
|
||||
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_popup.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
version_type: versionType,
|
||||
uploader: userName,
|
||||
files: JSON.stringify(files.map(f=>({
|
||||
name: f.name,
|
||||
original: f.orig.name,
|
||||
path: f.path,
|
||||
type: f.filetype,
|
||||
size: f.size
|
||||
})))
|
||||
}, function(res){
|
||||
if(res.group){
|
||||
loadFileList(productCode);
|
||||
alert("업로드 및 DB 저장 성공!");
|
||||
}
|
||||
}, "json");
|
||||
|
||||
$('#upload-modal').addClass('hidden');
|
||||
|
||||
} catch(err){
|
||||
alert("업로드 중 오류: "+err.message);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
const productCode = $('#product-select').val();
|
||||
loadFileList(productCode);
|
||||
loadVersion(productCode);
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
});
|
||||
|
||||
function loadFileList(productCode){
|
||||
const showDeleted = $("#show-deleted").is(":checked") ? 1 : 0;
|
||||
|
||||
$.ajax({
|
||||
url: "/egbim/sw_upload/upload_list.php",
|
||||
type: "GET",
|
||||
data: { product_code: productCode, show_deleted: showDeleted },
|
||||
dataType: "json",
|
||||
success: function(res){
|
||||
const $tbody = $("#file-list-" + productCode);
|
||||
$tbody.empty();
|
||||
|
||||
if(res.status !== "ok"){
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-red-500">조회 실패: ${res.message}</td></tr>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(res.files && res.files.length > 0){
|
||||
renderFilesFromDB(res.files, $tbody);
|
||||
} else {
|
||||
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-gray-500">데이터가 없습니다.</td></tr>`);
|
||||
}
|
||||
},
|
||||
error: function(xhr){
|
||||
alert("리스트 조회 중 오류가 발생했습니다. (code: "+xhr.status+")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", "#show-deleted", function(){
|
||||
const productCode = $("#product-select").val();
|
||||
loadFileList(productCode);
|
||||
});
|
||||
|
||||
function renderFilesFromDB(files, $tbody){
|
||||
let grouped = {};
|
||||
files.forEach(f=>{
|
||||
let key = f.sw_version+"_"+f.upload_date;
|
||||
if(!grouped[key]) grouped[key] = {meta:f, files:[]};
|
||||
grouped[key].files.push(f);
|
||||
});
|
||||
|
||||
Object.values(grouped).forEach(g=>{
|
||||
let rowspan = g.files.length;
|
||||
g.files.forEach((f, idx)=>{
|
||||
// ✅ 직접 다운로드 API 주소 생성 (filepath와 filename을 인자로 전달)
|
||||
const downloadUrl = `/egbim/sw_upload/upload_download.php?path=${encodeURIComponent(f.filepath)}&name=${encodeURIComponent(f.filename)}`;
|
||||
|
||||
$tbody.append(`
|
||||
<tr class="border-b">
|
||||
${idx===0 ? `
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}" rowspan="${rowspan}">
|
||||
${f.deleted==1 ? '' : `<input type="checkbox" value="${f.product_code}|${f.sw_version}"/>`}
|
||||
</td>
|
||||
<td class="px-4 py-2 font-bold text-blue-600 text-center" rowspan="${rowspan}">
|
||||
${f.sw_version}
|
||||
</td>
|
||||
` : ''}
|
||||
|
||||
<td class="px-4 py-2 ${f.deleted==1?'bg-red-100':''}">
|
||||
<a href="${downloadUrl}" class="text-blue-600 hover:text-blue-800 hover:underline flex items-center" target="_blank">
|
||||
<i class="fa-solid fa-file-arrow-down mr-2 opacity-70"></i>
|
||||
${f.filename}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">${f.filetype}</td>
|
||||
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">
|
||||
${f.filetype==="md"
|
||||
? (Math.round(f.filesize/1024*100)/100)+"KB"
|
||||
: (Math.round(f.filesize/1024/1024*100)/100)+"MB"}
|
||||
</td>
|
||||
|
||||
${idx===0?`
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.upload_date}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.uploader}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.test_done_date
|
||||
? f.test_done_date
|
||||
: `<button class="bg-green-500 text-white px-2 py-1 rounded btn-testdone"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">완료확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.tester || ''}</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
||||
${f.deleted==1
|
||||
? '<span class="text-red-500">삭제됨</span>'
|
||||
: (f.release_date
|
||||
? f.release_date
|
||||
: `<button class="bg-blue-500 text-white px-2 py-1 rounded btn-release"
|
||||
data-product="${f.product_code}"
|
||||
data-version="${f.sw_version}">배포확정</button>`)}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.releaser || ''}</td>
|
||||
`:''}
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#product-select').on('change', function(){
|
||||
const newCode = $(this).val();
|
||||
$(".file-list").addClass("hidden");
|
||||
$("#file-list-" + newCode).removeClass("hidden");
|
||||
loadFileList(newCode);
|
||||
loadVersion(newCode);
|
||||
});
|
||||
|
||||
function updateVersionType() {
|
||||
const major = parseInt($('#sw1').val() || 0, 10);
|
||||
const minor = parseInt($('#sw2').val() || 0, 10);
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
|
||||
let typeText = 'MAJOR';
|
||||
if (patch > 0) {
|
||||
typeText = 'PATCH';
|
||||
} else if (minor > 0) {
|
||||
typeText = 'MINOR';
|
||||
} else if (major > 0) {
|
||||
typeText = 'MAJOR';
|
||||
}
|
||||
|
||||
$('#version-type-text').text(typeText);
|
||||
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
}
|
||||
|
||||
$('#sw1, #sw2, #sw3').on('input', updateVersionType);
|
||||
updateVersionType();
|
||||
|
||||
$(document).on('click', '.btn-testdone', function(){
|
||||
const productCode = $(this).data('product');
|
||||
const swVersion = $(this).data('version');
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
$.post("/egbim/sw_upload/upload_testdone.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
tester: userName
|
||||
}, function(res){
|
||||
if(res.test_done_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(7).text(res.test_done_date);
|
||||
$(this).find("td").eq(8).text(res.tester);
|
||||
}
|
||||
});
|
||||
alert("테스트 완료되었습니다.");
|
||||
loadFileList(productCode);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-release", function(){
|
||||
const productCode = $(this).data("product");
|
||||
const swVersion = $(this).data("version");
|
||||
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
||||
|
||||
// ✅ 1. 사용자에게 제시할 확인 문구 설정
|
||||
const confirmText = "배포 완료 요청";
|
||||
|
||||
// ✅ 2. 입력창(prompt) 띄우기
|
||||
const userInput = prompt(`[버전 ${swVersion}] 릴리즈 서버로 이동하시겠습니까?\n\n진행하시려면 하단에 "${confirmText}" 문구를 정확히 입력해주세요.`);
|
||||
|
||||
// ✅ 3. 검증 로직
|
||||
if (userInput === null) return; // '취소'를 누른 경우
|
||||
|
||||
if (userInput.trim() !== confirmText) {
|
||||
alert(`입력한 문구가 일치하지 않습니다.\n(입력: ${userInput})`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 4. 문구가 일치할 경우에만 기존 서버 통신(AJAX) 실행
|
||||
$.post("/egbim/sw_upload/upload_release.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
releaser: userName
|
||||
}, function(res){
|
||||
if(res.release_date){
|
||||
$(`#file-list tr`).each(function(){
|
||||
const version = $(this).find("td:nth-child(2)").text().trim();
|
||||
if(version === swVersion){
|
||||
$(this).find("td").eq(9).text(res.release_date);
|
||||
$(this).find("td").eq(10).text(res.releaser);
|
||||
}
|
||||
});
|
||||
alert(`[버전 ${swVersion}] 배포가 성공적으로 확정되었습니다.`);
|
||||
loadFileList(productCode);
|
||||
} else {
|
||||
alert("실패: " + (res.message || "배포 확정 처리 중 오류 발생"));
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$(document).on("click", "#btn-delete", function () {
|
||||
const checked = $("input[type=checkbox]:checked").map(function () { return $(this).val(); }).get();
|
||||
if (checked.length === 0) {
|
||||
alert("삭제할 버전을 선택하세요.");
|
||||
return;
|
||||
}
|
||||
if (!confirm("선택한 버전 전체 파일을 삭제하시겠습니까?")) return;
|
||||
|
||||
checked.forEach(item => {
|
||||
const [product, version] = item.split("|");
|
||||
$.post("/egbim/sw_upload/upload_delete.php", {
|
||||
product_code: product,
|
||||
sw_version: version
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert(`[${version}] 삭제 완료!`);
|
||||
loadFileList(product);
|
||||
} else {
|
||||
if (res.failed && res.failed.length > 0) {
|
||||
let errors = res.failed.map(f => `${f.file}: ${f.error}`).join("\n");
|
||||
alert("삭제 실패:\n" + errors);
|
||||
} else {
|
||||
alert("삭제 실패: " + (res.message || "알 수 없는 오류"));
|
||||
}
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 저장/변경 버튼 클릭 이벤트 수정
|
||||
$(document).on("click", "#save-version", function() {
|
||||
const productCode = $("#product-select").val();
|
||||
const version = $("#external-version").val(); // "사용안함" 선택 시 "" 값이 전달됨
|
||||
|
||||
const msg = (version && version !== "")
|
||||
? `외부 배포를 [ ${version} ] 버전으로 변경하시겠습니까?`
|
||||
: `외부 배포를 '사용안함'으로 설정하시겠습니까?`;
|
||||
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
$.post("/egbim/sw_upload/upload_version_update.php", {
|
||||
product_code: productCode,
|
||||
version: version // 백엔드에서 ""를 받으면 내부 버전과 동일하게 맞춰 "사용안함" 처리함
|
||||
}, function(res) {
|
||||
if (res.status === "ok") {
|
||||
alert("배포 버전 설정이 완료되었습니다.");
|
||||
loadVersion(productCode); // UI 새로고침
|
||||
} else {
|
||||
alert("설정 실패: " + res.message);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
|
||||
// ✅ 외부 배포 버전 정보 및 선택 목록 로드 함수 수정
|
||||
function loadVersion(productCode) {
|
||||
$.getJSON("/egbim/sw_upload/upload_version_get.php", { product_code: productCode }, function(res) {
|
||||
if (res.status === "ok") {
|
||||
// ✅ 이 코드가 있어야 "0.0.0" 대신 "1.2.0" 등의 실제 버전이 화면에 보입니다.
|
||||
$("#latest-family").text(res.family_version);
|
||||
|
||||
const $sel = $("#external-version");
|
||||
$sel.empty();
|
||||
|
||||
// 목록 그리기
|
||||
if (res.confirmed_versions) {
|
||||
res.confirmed_versions.forEach(v => {
|
||||
const label = (v === "") ? "사용안함" : v;
|
||||
$sel.append(`<option value="${v}">${label}</option>`);
|
||||
});
|
||||
}
|
||||
|
||||
$sel.val(res.external_version);
|
||||
|
||||
if (res.external_version !== "") {
|
||||
$("#save-version").text("변경").removeClass("bg-blue-500").addClass("bg-yellow-500");
|
||||
} else {
|
||||
$("#save-version").text("저장").removeClass("bg-yellow-500").addClass("bg-blue-500");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindRangeCorrection(selector) {
|
||||
$(selector).on("input", function() {
|
||||
let val = parseInt($(this).val(), 10);
|
||||
let min = parseInt($(this).attr("min"), 10) || 0;
|
||||
let max = parseInt($(this).attr("max"), 10);
|
||||
|
||||
if (isNaN(val)) {
|
||||
$(this).val("");
|
||||
} else if (val < min) {
|
||||
$(this).val(min);
|
||||
} else if (val > max) {
|
||||
$(this).val(max);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindRangeCorrection("#sw1, #sw2, #sw3");
|
||||
|
||||
$('#btn-new').on('click', ()=>{
|
||||
$('#setup-file, #patch-file, #release-file, #info-file').val('');
|
||||
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
|
||||
|
||||
const latestVer = $('#latest-family').text().trim();
|
||||
if (latestVer) {
|
||||
const parts = latestVer.split('.');
|
||||
$('#sw1').val(parts[0] || 0);
|
||||
$('#sw2').val(parts[1] || 0);
|
||||
$('#sw3').val(parts[2] || 0);
|
||||
}
|
||||
|
||||
const patch = parseInt($('#sw3').val() || 0, 10);
|
||||
if (patch > 0) {
|
||||
$('#patch-file').prop('disabled', false);
|
||||
$('#patch-filename').prop('disabled', false);
|
||||
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
||||
} else {
|
||||
$('#patch-file').prop('disabled', true).val('');
|
||||
$('#patch-filename').prop('disabled', true).val('');
|
||||
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
||||
}
|
||||
|
||||
updateVersionType();
|
||||
$('#upload-modal').removeClass('hidden');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// ✅ 핵심: 숨어있는 모든 DB 에러를 밖으로 던지도록 강력한 에러 리포팅 설정
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
try {
|
||||
// DB 연결
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
$conn->set_charset('utf8mb4');
|
||||
|
||||
// === 파라미터 받기 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$version_type = $_POST['version_type'] ?? 'patch'; // major/minor/patch
|
||||
$uploader = $_POST['uploader'] ?? '알 수 없음';
|
||||
$files = isset($_POST['files']) ? json_decode($_POST['files'], true) : [];
|
||||
|
||||
if (!$product_code || !$sw_version || empty($files)) {
|
||||
throw new Exception("필수값 누락 (제품코드, 버전, 파일 정보 중 하나가 없습니다)");
|
||||
}
|
||||
|
||||
// ✅ 트랜잭션 시작 (중간에 에러나면 데이터베이스를 롤백해서 데이터가 꼬이지 않게 보호함)
|
||||
$conn->begin_transaction();
|
||||
|
||||
$inserted = [];
|
||||
|
||||
foreach ($files as $f) {
|
||||
$filename = $f['name'] ?? '';
|
||||
$original_name = $f['original'] ?? $filename;
|
||||
$filepath = $f['path'] ?? '';
|
||||
$filetype = $f['type'] ?? '';
|
||||
$filesize = $f['size'] ?? 0;
|
||||
|
||||
$sql = "
|
||||
INSERT INTO deploy_files
|
||||
(product_code, sw_version, version_type,
|
||||
filename, original_name, filepath, filetype,
|
||||
filesize, uploader, upload_date, deleted)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
filename = VALUES(filename),
|
||||
original_name = VALUES(original_name),
|
||||
filepath = VALUES(filepath),
|
||||
filesize = VALUES(filesize),
|
||||
uploader = VALUES(uploader),
|
||||
upload_date = NOW(),
|
||||
deleted = 0
|
||||
";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
|
||||
// ✅ 용량이 매우 큰 파일(2GB 이상)의 경우 정수형(i) 바인딩 시 에러가 날 수 있어, 모두 문자열(s)로 바인딩하여 안전하게 MySQL에 넘깁니다.
|
||||
$stmt->bind_param(
|
||||
"sssssssss",
|
||||
$product_code,
|
||||
$sw_version,
|
||||
$version_type,
|
||||
$filename,
|
||||
$original_name,
|
||||
$filepath,
|
||||
$filetype,
|
||||
$filesize,
|
||||
$uploader
|
||||
);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$inserted[] = [
|
||||
'filename' => $filename,
|
||||
'original' => $original_name,
|
||||
'filetype' => $filetype,
|
||||
'version' => $sw_version,
|
||||
'size' => round($filesize/1024/1024, 2)."MB"
|
||||
];
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
/* ✅ 버전 기록: deploy_versions_test 업데이트 */
|
||||
$check = $conn->prepare("SELECT COUNT(*) FROM deploy_versions_test WHERE product_code=?");
|
||||
$check->bind_param("s", $product_code);
|
||||
$check->execute();
|
||||
$check->bind_result($cnt);
|
||||
$check->fetch();
|
||||
$check->close();
|
||||
|
||||
if ($cnt > 0) {
|
||||
// 기존 레코드 갱신
|
||||
$upd = $conn->prepare("
|
||||
UPDATE deploy_versions_test
|
||||
SET family_version=?, updated_at=NOW()
|
||||
WHERE product_code=?
|
||||
");
|
||||
$upd->bind_param("ss", $sw_version, $product_code);
|
||||
$upd->execute();
|
||||
$upd->close();
|
||||
} else {
|
||||
// 신규 추가
|
||||
$ins = $conn->prepare("
|
||||
INSERT INTO deploy_versions_test (product_code, family_version, updated_at)
|
||||
VALUES (?, ?, NOW())
|
||||
");
|
||||
$ins->bind_param("ss", $product_code, $sw_version);
|
||||
$ins->execute();
|
||||
$ins->close();
|
||||
}
|
||||
|
||||
// ✅ 위 모든 쿼리가 단 하나의 에러 없이 정상 실행되었을 때만 커밋(진짜 저장)
|
||||
$conn->commit();
|
||||
|
||||
// 프론트엔드로 성공 메시지 전달
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => '업로드 DB 기록 완료',
|
||||
'group' => [
|
||||
'files' => $inserted,
|
||||
'upload' => date('Y-m-d H:i:s'),
|
||||
'uploader' => $uploader,
|
||||
'testdone' => '<button class="bg-green-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
'tester' => '',
|
||||
'release' => '<button class="bg-blue-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
'releaser' => ''
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// ✅ 쿼리 중 하나라도 에러가 나면 여기로 빠져나와서 롤백 처리
|
||||
if (isset($conn) && $conn->ping()) {
|
||||
$conn->rollback();
|
||||
}
|
||||
|
||||
// 이 메시지가 프론트엔드의 alert 창에 그대로 뜨게 됩니다!
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'DB 에러 상세 원인: ' . $e->getMessage()
|
||||
]);
|
||||
} finally {
|
||||
if (isset($conn) && $conn->ping()) {
|
||||
$conn->close();
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Credentials\Credentials;
|
||||
|
||||
// === 버킷/키 설정 ===
|
||||
$bucket_name = "baron-software-test";
|
||||
$account_id = "81fa2d48964d31dd0da9558f9ce601d1";
|
||||
$access_key = "9ade5915f7abd402db60a87f3b3b6d76";
|
||||
$secret_key = "a1a1cfd59f739977f98fb73f214894e39dbac9a39b1f6208020aa2e1303ea54c";
|
||||
|
||||
// === 프론트에서 넘어온 값 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$filename = $_POST['filename'] ?? '';
|
||||
$content_type = $_POST['content_type'] ?? 'application/octet-stream';
|
||||
|
||||
if (!$product_code || !$sw_version || !$filename) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => '필수값 누락 (product_code, sw_version, filename)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 제품 코드 → 실제 폴더명 매핑 ===
|
||||
$map = [
|
||||
"1" => "eg-bim",
|
||||
"2" => "tova",
|
||||
"3" => "gaia",
|
||||
"999" => "test"
|
||||
];
|
||||
|
||||
$folder = $map[$product_code] ?? $product_code; // 혹시 매핑 없으면 그대로 사용
|
||||
|
||||
// === 업로드 경로 ===
|
||||
// ex) eg-bim/1.2.3/setup_eg-bim_1.2.3.exe
|
||||
$key = "{$folder}/{$sw_version}/{$filename}";
|
||||
|
||||
try {
|
||||
$credentials = new Credentials($access_key, $secret_key);
|
||||
|
||||
$s3 = new S3Client([
|
||||
'region' => 'auto',
|
||||
'version' => 'latest',
|
||||
'endpoint' => "https://{$account_id}.r2.cloudflarestorage.com",
|
||||
'credentials' => $credentials,
|
||||
]);
|
||||
|
||||
// Presign URL 생성
|
||||
$cmd = $s3->getCommand('PutObject', [
|
||||
'Bucket' => $bucket_name,
|
||||
'Key' => $key,
|
||||
'ContentType' => $content_type
|
||||
]);
|
||||
|
||||
$request = $s3->createPresignedRequest($cmd, '+1 hour');
|
||||
|
||||
echo json_encode([
|
||||
'url' => (string)$request->getUri(), // 브라우저에서 PUT 할 주소
|
||||
'key' => $key // DB에 저장할 전체 경로
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'error' => 'Presign 실패',
|
||||
'detail' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
// ini_set('display_errors', 1);
|
||||
// ini_set('display_startup_errors', 1);
|
||||
// error_reporting(E_ALL);
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// require __DIR__ . '/../vendor/autoload.php'; // AWS SDK 로드
|
||||
|
||||
// use Aws\S3\S3Client;
|
||||
// use Aws\Exception\AwsException;
|
||||
|
||||
// // === DB 연결 ===
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 파라미터 ===
|
||||
// $file_id = $_POST['id'] ?? 0;
|
||||
// $releaser = $_POST['releaser'] ?? '관리자';
|
||||
|
||||
// if (!$file_id) {
|
||||
// http_response_code(400);
|
||||
// echo json_encode(['message' => '필수값 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === 파일 정보 가져오기 ===
|
||||
// $res = $conn->query("SELECT * FROM deploy_files WHERE id=".(int)$file_id);
|
||||
// $file = $res->fetch_assoc();
|
||||
|
||||
// if (!$file) {
|
||||
// http_response_code(404);
|
||||
// echo json_encode(['message' => '파일 정보 없음']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// $sourceBucket = 'baron-software-test';
|
||||
// $targetBucket = 'baron-software-release';
|
||||
// $objectKey = $file['filepath'];
|
||||
|
||||
// // === S3 클라이언트 생성 ===
|
||||
// // Test 버킷 (Read/Delete)
|
||||
// $testS3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '9ade5915f7abd402db60a87f3b3b6d76', // ✅ write key
|
||||
// 'secret' => 'a1a1cfd59f739977f98fb73f214894e39dbac9a39b1f6208020aa2e1303ea54c',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// // Release 버킷 (Write)
|
||||
// $releaseS3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '4d0b8feaf0e12873a3d95bd3aa0b787c',
|
||||
// 'secret' => 'dc72a027e5263590f0bc89fc0b5a1e6c846cc4f036642430681364c4a9056ca4',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// try {
|
||||
// // 1. 원본 객체 다운로드
|
||||
// $object = $testS3->getObject([
|
||||
// 'Bucket' => $sourceBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 2. Release 버킷에 업로드
|
||||
// $releaseS3->putObject([
|
||||
// 'Bucket' => $targetBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// 'Body' => $object['Body'],
|
||||
// ]);
|
||||
|
||||
// // 3. Test 버킷에서 원본 삭제
|
||||
// $testS3->deleteObject([
|
||||
// 'Bucket' => $sourceBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 4. DB 업데이트
|
||||
// $stmt = $conn->prepare("UPDATE deploy_files SET release_date=NOW(), releaser=? WHERE id=?");
|
||||
// $stmt->bind_param("si", $releaser, $file_id);
|
||||
// $stmt->execute();
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => 'ok',
|
||||
// 'message' => '릴리즈 확정 완료',
|
||||
// 'targetKey' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// } catch (AwsException $e) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode([
|
||||
// 'status' => 'fail',
|
||||
// 'message' => '릴리즈 실패: '.$e->getAwsErrorMessage(),
|
||||
// 'error' => $e->getMessage()
|
||||
// ]);
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php'; // AWS SDK 로드
|
||||
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
// === DB 연결 ===
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
// 릴리즈 처리 → releaser를 세션에서 가져옴
|
||||
$releaser = $_POST['releaser'] ?? '알 수 없음'; // JS에서 넘겨준 userName
|
||||
$loginId = $_POST['loginId'] ?? ''; // JS에서 넘겨준 loginId
|
||||
|
||||
if (!$product_code || !$sw_version) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '필수값 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 대상 파일 목록 조회 ===
|
||||
$sql = "SELECT * FROM deploy_files WHERE product_code=? AND sw_version=? AND deleted=0";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("ss", $product_code, $sw_version);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
|
||||
$files = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$files[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
if (empty($files)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['message' => '해당 버전에 대한 파일이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ 테스트 완료 여부 체크
|
||||
if (empty($files[0]['test_done_date'])) {
|
||||
echo json_encode(['status'=>'fail','message'=>'테스트 완료 후 릴리즈 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 버킷 설정 ===
|
||||
$sourceBucket = 'baron-software-test';
|
||||
$targetBucket = 'baron-software-release';
|
||||
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
// ❗ source(read/write) + target(write) 권한이 모두 있는 키
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
]
|
||||
]);
|
||||
|
||||
$success = [];
|
||||
$failed = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$objectKey = $file['filepath'];
|
||||
|
||||
try {
|
||||
// 1. CopyObject (테스트 → 릴리즈)
|
||||
$s3->copyObject([
|
||||
'Bucket' => $targetBucket,
|
||||
'Key' => $objectKey,
|
||||
'CopySource' => $sourceBucket . '/' . $objectKey,
|
||||
]);
|
||||
|
||||
// 2. 원본 삭제 (테스트 버킷)
|
||||
$s3->deleteObject([
|
||||
'Bucket' => $sourceBucket,
|
||||
'Key' => $objectKey,
|
||||
]);
|
||||
|
||||
$success[] = $objectKey;
|
||||
} catch (AwsException $e) {
|
||||
$failed[] = $objectKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($failed)) {
|
||||
// ❌ 하나라도 실패 → 전체 실패 처리
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '릴리즈 실패: 일부 파일 이동 오류',
|
||||
'failed' => $failed
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ 여기까지 오면 전체 이동 성공
|
||||
// 1) deploy_files 업데이트
|
||||
$stmt = $conn->prepare("
|
||||
UPDATE deploy_files
|
||||
SET release_date = NOW(), releaser = ?
|
||||
WHERE product_code=? AND sw_version=? AND deleted=0
|
||||
");
|
||||
$stmt->bind_param("sss", $releaser, $product_code, $sw_version);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// 2) deploy_versions.family_version 갱신
|
||||
$stmt2 = $conn->prepare("
|
||||
INSERT INTO deploy_versions (product_code, family_version, updated_at)
|
||||
VALUES (?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version = VALUES(family_version),
|
||||
updated_at = NOW()
|
||||
");
|
||||
$stmt2->bind_param("ss", $product_code, $sw_version);
|
||||
$stmt2->execute();
|
||||
$stmt2->close();
|
||||
|
||||
// 최종 응답
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'message' => '릴리즈 확정 완료',
|
||||
'moved' => $success,
|
||||
'release_date' => date('Y-m-d H:i:s'),
|
||||
'releaser' => $releaser
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
|
||||
// DB 연결
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 받기 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$tester = $_POST['tester'] ?? '알 수 없음';
|
||||
|
||||
if (!$product_code || !$sw_version) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '필수값 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 버전 단위 전체 업데이트 ===
|
||||
$sql = "UPDATE deploy_files
|
||||
SET test_done_date = NOW(), tester = ?
|
||||
WHERE sw_version = ?
|
||||
AND product_code = ?"; // 순서 변경
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("sss", $tester, $sw_version, $product_code);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode([
|
||||
'message' => '테스트 완료 업데이트 성공',
|
||||
'test_done_date' => date('Y-m-d H:i:s'),
|
||||
'tester' => $tester
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 업데이트 실패: '.$stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
"egbim", "baron3840!!",
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$productCode = $_GET['product_code'] ?? '';
|
||||
if (!$productCode) {
|
||||
echo json_encode(['status' => 'fail', 'message' => '제품코드 없음']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1️⃣ 현재 제품의 최신 내부/외부 버전 정보 가져오기
|
||||
// updated_at 기준으로 가장 최신 행 1개만 가져옵니다.
|
||||
$st = $pdo->prepare("
|
||||
SELECT family_version, external_version
|
||||
FROM deploy_versions
|
||||
WHERE product_code = ?
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$st->execute([$productCode]);
|
||||
$verRow = $st->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// 데이터가 아예 없을 경우를 대비한 기본값
|
||||
$familyVer = $verRow['family_version'] ?? '0.0.0';
|
||||
$externalVer = $verRow['external_version'] ?? '';
|
||||
|
||||
// 2️⃣ "사용안함" 로직: 내부와 외부 버전이 같으면 외부 버전은 빈 값으로 처리
|
||||
if ($externalVer === $familyVer) {
|
||||
$externalVer = '';
|
||||
}
|
||||
|
||||
// 3️⃣ 콤보박스에 들어갈 '배포 확정'된 버전 목록 가져오기 (deploy_files 테이블 기준)
|
||||
$st = $pdo->prepare("
|
||||
SELECT DISTINCT sw_version
|
||||
FROM deploy_files
|
||||
WHERE product_code = ?
|
||||
AND release_date IS NOT NULL
|
||||
AND deleted = 0
|
||||
ORDER BY
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
");
|
||||
$st->execute([$productCode]);
|
||||
$allVersions = $st->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// 4️⃣ 필터링 및 "사용안함" 옵션 추가
|
||||
$filtered = [];
|
||||
foreach ($allVersions as $v) {
|
||||
// 내부 배포 버전(최신 작업물)보다 높은 버전은 목록에서 제외 (논리적 오류 방지)
|
||||
if (version_compare($v, $familyVer, '>')) continue;
|
||||
$filtered[] = $v;
|
||||
}
|
||||
|
||||
// 목록 최상단에 항상 "사용안함" 추가
|
||||
array_unshift($filtered, "");
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'family_version' => $familyVer, // 프론트의 #latest-family에 표시됨
|
||||
'external_version' => $externalVer, // 현재 선택된 값
|
||||
'external_exists' => ($externalVer !== ''),
|
||||
'confirmed_versions' => $filtered
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 'fail', 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
// require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
|
||||
// try {
|
||||
// $pdo = new PDO(
|
||||
// "mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
// "egbim", "baron3840!!",
|
||||
// [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
// );
|
||||
|
||||
// $productCode = $_POST['product_code'] ?? '';
|
||||
// $version = $_POST['version'] ?? '';
|
||||
// $userName = $_SESSION['user']['name'] ?? '알수없음';
|
||||
|
||||
// if(!$productCode){
|
||||
// echo json_encode(['status'=>'fail','message'=>'제품 코드 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // ✅ 버전이 비어있으면 기본값 "0.0.0"
|
||||
// $version = trim($version) !== '' ? $version : '0.0.0';
|
||||
|
||||
// // ✅ family_version = external_version 동일하게 저장
|
||||
// $st = $pdo->prepare("
|
||||
// INSERT INTO deploy_versions (product_code, family_version, external_version, external_updated_by, updated_at)
|
||||
// VALUES (:pc, :ver, :ver, :user, NOW())
|
||||
// ON DUPLICATE KEY UPDATE
|
||||
// family_version = VALUES(family_version),
|
||||
// external_version = VALUES(external_version),
|
||||
// external_updated_by = VALUES(external_updated_by),
|
||||
// updated_at = NOW()
|
||||
// ");
|
||||
// $st->execute([
|
||||
// ':pc' => $productCode,
|
||||
// ':ver' => $version,
|
||||
// ':user' => $userName
|
||||
// ]);
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => 'ok',
|
||||
// 'family_version' => $version,
|
||||
// 'external_version' => $version,
|
||||
// 'updated_by' => $userName
|
||||
// ]);
|
||||
|
||||
// } catch(Exception $e){
|
||||
// echo json_encode(['status'=>'fail','message'=>$e->getMessage()]);
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
// require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
|
||||
// try {
|
||||
// $pdo = new PDO(
|
||||
// "mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
// "egbim", "baron3840!!",
|
||||
// [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
// );
|
||||
|
||||
// $productCode = $_POST['product_code'] ?? '';
|
||||
// $version = trim($_POST['version'] ?? ''); // 프론트에서 "" 혹은 "1.2.3" 전달
|
||||
// $userName = $_SESSION['user']['name'] ?? '알수없음';
|
||||
|
||||
// if(!$productCode){
|
||||
// echo json_encode(['status'=>'fail','message'=>'제품 코드 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // 1. 현재 테이블의 최신 정보를 먼저 가져옵니다 (내부 버전 유지 목적)
|
||||
// $st = $pdo->prepare("SELECT family_version FROM deploy_versions WHERE product_code=? ORDER BY updated_at DESC LIMIT 1");
|
||||
// $st->execute([$productCode]);
|
||||
// $currentFamily = $st->fetchColumn() ?: '0.0.0';
|
||||
|
||||
// // 2. "사용안함" 처리 로직
|
||||
// // upload_version_get.php 로직에 맞춰 "사용안함"일 때는 외부 버전을 내부 버전과 동일하게 세팅
|
||||
// if ($version === '' || $version === '0.0.0') {
|
||||
// $targetExternal = $currentFamily;
|
||||
// } else {
|
||||
// $targetExternal = $version;
|
||||
// }
|
||||
|
||||
// // 3. DB 업데이트
|
||||
// $st = $pdo->prepare("
|
||||
// INSERT INTO deploy_versions (product_code, family_version, external_version, external_updated_by, updated_at)
|
||||
// VALUES (:pc, :family, :external, :user, NOW())
|
||||
// ON DUPLICATE KEY UPDATE
|
||||
// family_version = VALUES(family_version),
|
||||
// external_version = VALUES(external_version),
|
||||
// external_updated_by = VALUES(external_updated_by),
|
||||
// updated_at = NOW()
|
||||
// ");
|
||||
|
||||
// $st->execute([
|
||||
// ':pc' => $productCode,
|
||||
// ':family' => $currentFamily, // 내부 버전은 그대로 유지
|
||||
// ':external' => $targetExternal, // 외부 버전만 변경
|
||||
// ':user' => $userName
|
||||
// ]);
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => 'ok',
|
||||
// 'family_version' => $currentFamily,
|
||||
// 'external_version' => ($version === '' ? '' : $version),
|
||||
// 'updated_by' => $userName
|
||||
// ]);
|
||||
|
||||
// } catch(Exception $e){
|
||||
// echo json_encode(['status'=>'fail','message'=>$e->getMessage()]);
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
"egbim", "baron3840!!",
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
// ✅ 프론트에서 넘어온 값이 빈 문자열("")이면 "사용안함"을 의미함
|
||||
$version = isset($_POST['version']) ? trim($_POST['version']) : '';
|
||||
$userName = $_SESSION['user']['name'] ?? '알수없음';
|
||||
|
||||
if(!$productCode){
|
||||
echo json_encode(['status'=>'fail','message'=>'제품 코드 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1️⃣ 현재 DB에 저장된 내부 최신 버전(family_version)을 가져옵니다.
|
||||
// 외부 버전만 바꿀 때 내부 버전 정보를 잃어버리지 않기 위함입니다.
|
||||
$st = $pdo->prepare("SELECT family_version FROM deploy_versions WHERE product_code=? ORDER BY updated_at DESC LIMIT 1");
|
||||
$st->execute([$productCode]);
|
||||
$currentFamily = $st->fetchColumn();
|
||||
|
||||
// 만약 데이터가 아예 없다면 기본값 설정
|
||||
if (!$currentFamily) $currentFamily = '0.0.0';
|
||||
|
||||
// 2️⃣ "사용안함" 처리 핵심 로직
|
||||
// GET API 로직(if $externalVer === $familyVer)에 맞춰서,
|
||||
// 사용자가 "사용안함"을 선택했다면 external_version을 family_version과 똑같이 맞춰 저장합니다.
|
||||
if ($version === '') {
|
||||
$targetExternal = $currentFamily; // 내부 버전과 동일하게 변경
|
||||
} else {
|
||||
$targetExternal = $version; // 선택한 특정 버전으로 변경
|
||||
}
|
||||
|
||||
// 3️⃣ DB 업데이트 (INSERT ... ON DUPLICATE KEY UPDATE)
|
||||
// product_code가 PK이거나 Unique 인덱스여야 기존 데이터가 수정됩니다.
|
||||
$st = $pdo->prepare("
|
||||
INSERT INTO deploy_versions
|
||||
(product_code, family_version, external_version, external_updated_by, updated_at)
|
||||
VALUES
|
||||
(:pc, :family, :external, :user, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version = VALUES(family_version),
|
||||
external_version = VALUES(external_version),
|
||||
external_updated_by = VALUES(external_updated_by),
|
||||
updated_at = NOW()
|
||||
");
|
||||
|
||||
$st->execute([
|
||||
':pc' => $productCode,
|
||||
':family' => $currentFamily,
|
||||
':external' => $targetExternal,
|
||||
':user' => $userName
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'family_version' => $currentFamily,
|
||||
'external_version' => $targetExternal, // 외부 버전은 targetExternal로 설정
|
||||
'updated_by' => $userName
|
||||
]);
|
||||
|
||||
} catch(Exception $e){
|
||||
echo json_encode(['status'=>'fail', 'message'=> $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user