260 lines
8.9 KiB
PHP
260 lines
8.9 KiB
PHP
<?php
|
|
ini_set('display_errors', 1);
|
|
error_reporting(E_ALL);
|
|
header('Content-Type: application/json; charset=UTF-8');
|
|
set_time_limit(600);
|
|
// file_put_contents(__DIR__.'/debug_tenant.log', print_r($input, true), FILE_APPEND);
|
|
|
|
// === 환경변수 (실서비스 시 교체)
|
|
$PROJECT_ID = 'P2wON5fy1K6kyia269VpeIzYP8oP';
|
|
$MGMT_KEY = 'K32l5ORmzy32OvaaPvpdZsMY3JmKQb7a3vvrl10PgjlJUGk3K7EssMH3uW5VGQSbrgtEdPj';
|
|
// ✅ Tenant ID 매핑 테이블
|
|
$TENANT_IDS = [
|
|
'customer' => 'T2x4TDzxasp7auPCPcN8uOrxXchh',
|
|
'family' => 'T2wQcWCBhUfJgUWHWgNwLg4iUDVY',
|
|
'edu' => 'T31ZmUcwOZbwk0y3YmMxrPCpzpQR',
|
|
'egBIM' => 'T2yGFrGSnFX601G22JOMojJX7OMd',
|
|
];
|
|
|
|
// === 입력값(JSON)
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$csvUrl = trim($input['csv_url'] ?? '');
|
|
$expiryIn = trim($input['expiry'] ?? '');
|
|
$sendReset = !empty($input['send_reset']);
|
|
$tenantType = trim($input['tenant_type'] ?? '');
|
|
// echo json_encode(['tenantType' => $tenantType]);
|
|
// exit;
|
|
|
|
if (!$csvUrl) {
|
|
echo json_encode(['status'=>'fail','message'=>'CSV URL 누락']);
|
|
exit;
|
|
}
|
|
|
|
// ✅ cURL 방식으로 CSV 가져오기 (Cafe24 호환)
|
|
function fetch_csv($url) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
CURLOPT_SSL_VERIFYHOST => false,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP CSV Importer)',
|
|
]);
|
|
$data = curl_exec($ch);
|
|
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$err = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($httpcode !== 200 || !$data) {
|
|
throw new Exception("CSV 다운로드 실패 (HTTP {$httpcode}) {$err}");
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
try {
|
|
$csv = fetch_csv($csvUrl);
|
|
|
|
// ✅ UTF-8 BOM 제거
|
|
if (substr($csv, 0, 3) === "\xEF\xBB\xBF") {
|
|
$csv = substr($csv, 3);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode(['status'=>'fail','message'=>'CSV 파일을 불러올 수 없습니다: '.$e->getMessage()]);
|
|
exit;
|
|
}
|
|
|
|
$rows = array_map('str_getcsv', preg_split("/\r\n|\n|\r/", trim($csv)));
|
|
if (count($rows) < 2) {
|
|
echo json_encode(['status'=>'fail','message'=>'CSV 데이터 없음']);
|
|
exit;
|
|
}
|
|
|
|
// ✅ 헤더 정규화 (BOM, 공백, 대소문자 통일)
|
|
$header = array_map(function($h) {
|
|
$h = trim($h);
|
|
$h = preg_replace('/^\xEF\xBB\xBF/', '', $h); // BOM 제거
|
|
return strtolower($h);
|
|
}, $rows[0]);
|
|
file_put_contents(__DIR__.'/debug_header.log', print_r($header, true));
|
|
$dataRows = array_slice($rows, 1);
|
|
|
|
// === 유틸 함수 ===
|
|
function is_true($v){ $s = strtolower(trim((string)$v)); return in_array($s, ['true','1','y','yes'], true); }
|
|
function parse_list($v){
|
|
$t = trim((string)$v);
|
|
if ($t === '') return [];
|
|
if ($t[0] === '[') { $a = json_decode($t, true); return is_array($a)? $a: []; }
|
|
return array_values(array_filter(array_map('trim', explode(',', $t)), fn($x)=>$x!==''));
|
|
}
|
|
function call_api($url, $opts){
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, $opts + [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>45]);
|
|
$body = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$err = curl_error($ch);
|
|
curl_close($ch);
|
|
return [$code, $body, $err];
|
|
}
|
|
function mgmt_api($path, $payload, $PROJECT_ID, $MGMT_KEY){
|
|
return call_api("https://api.descope.com/v1/mgmt/$path", [
|
|
CURLOPT_POST=>true,
|
|
CURLOPT_HTTPHEADER=>[
|
|
"Authorization: Bearer {$PROJECT_ID}:{$MGMT_KEY}",
|
|
"Content-Type: application/json",
|
|
],
|
|
CURLOPT_POSTFIELDS=> json_encode($payload, JSON_UNESCAPED_UNICODE),
|
|
]);
|
|
}
|
|
function update_password($PROJECT_ID, $MGMT_KEY, $email, $password){
|
|
$payload = ["loginId"=>$email, "newPassword"=>$password];
|
|
return call_api("https://api.descope.com/v1/mgmt/password/update", [
|
|
CURLOPT_POST=>true,
|
|
CURLOPT_HTTPHEADER=>[
|
|
"Authorization: Bearer {$PROJECT_ID}:{$MGMT_KEY}",
|
|
"Content-Type: application/json",
|
|
],
|
|
CURLOPT_POSTFIELDS=> json_encode($payload),
|
|
]);
|
|
}
|
|
|
|
// === 날짜 변환
|
|
$expiryISO = null;
|
|
if ($expiryIn !== '') {
|
|
$dt = date_create($expiryIn);
|
|
if ($dt) $expiryISO = $dt->format(DateTime::ATOM);
|
|
}
|
|
|
|
// === 메인 루프
|
|
$created=0; $updated=0; $failed=0; $errors=[];
|
|
|
|
foreach ($dataRows as $i => $row) {
|
|
if (count($row) !== count($header)) {
|
|
file_put_contents(__DIR__.'/debug_mismatch.log',
|
|
"Row ".($i+2)." 열 개수 불일치: ".count($row)." / ".count($header)." -> ".implode(',', $row)."\n",
|
|
FILE_APPEND
|
|
);
|
|
}
|
|
if (count($row) === 1 && trim($row[0])==='') continue;
|
|
$rec = array_combine($header, array_pad($row, count($header), ''));
|
|
|
|
$loginId = trim($rec['login_id'] ?? '');
|
|
$email = trim($rec['email'] ?? '');
|
|
if (!$loginId || !$email) {
|
|
$failed++; $errors[]=['row'=>$i+2,'error'=>'login_id/email 누락']; continue;
|
|
}
|
|
|
|
$status = trim($rec['status'] ?? 'activated');
|
|
$name = trim($rec['display_name'] ?? '');
|
|
$phone = trim($rec['Phone'] ?? '');
|
|
$verifiedEmail = true; // ✅ 모든 계정 이메일 인증 완료로 처리
|
|
$verifiedPhone = is_true($rec['Phone Verified'] ?? '');
|
|
$tenants = array_map(fn($t)=>['tenantId'=>$t], parse_list($rec['tenants'] ?? ''));
|
|
$roles = parse_list($rec['role'] ?? '');
|
|
$company = trim($rec['company'] ?? '');
|
|
// $completeForm = is_true($rec['completeForm'] ?? '');
|
|
$completeForm = true; //추가정보입력 무조건 완료
|
|
$expiryDate = $rec['egBimLExpiryDate'] ?? '';
|
|
$isTestUser = trim($rec['Is Test User'] ?? '');
|
|
$newPassword = trim($rec['new_password'] ?? '');
|
|
|
|
// ✅ 테넌트 자동 지정 (팝업 선택 우선)
|
|
if (isset($TENANT_IDS[$tenantType])) {
|
|
$tenants = [['tenantId' => $TENANT_IDS[$tenantType]]];
|
|
|
|
// ✅ 복수 등록 규칙
|
|
if ($tenantType === 'customer') {
|
|
$tenants[] = ['tenantId' => $TENANT_IDS['egBIM']];
|
|
}
|
|
} else {
|
|
// fallback (CSV 내 tenants 열 직접 입력 시)
|
|
$tenants = array_map(fn($t)=>['tenantId'=>$t], parse_list($rec['tenants'] ?? ''));
|
|
}
|
|
|
|
// customAttributes 구성
|
|
$custom = [
|
|
'company' => $company ?: null,
|
|
'completeForm' => $completeForm,
|
|
'isTestUser' => $isTestUser,
|
|
];
|
|
// 시트에서 만료일이 따로 있을 경우 우선
|
|
if ($expiryDate) {
|
|
$dt = date_create($expiryDate);
|
|
if ($dt) $custom['egBimLExpiryDate'] = $dt->format(DateTime::ATOM);
|
|
} elseif ($expiryISO) {
|
|
$custom['egBimLExpiryDate'] = $expiryISO;
|
|
}
|
|
|
|
// 생성/수정 페이로드
|
|
$payload = array_filter([
|
|
'loginId' => $loginId,
|
|
'email' => $email,
|
|
'name' => $name,
|
|
'phone' => $phone,
|
|
'status' => 'activated', // ✅ 명시적으로 활성상태
|
|
'invite' => false, // ✅ 초대 모드 비활성화
|
|
'verifiedEmail' => $verifiedEmail,
|
|
'verifiedPhone' => $verifiedPhone,
|
|
'password' => $newPassword ?: null, // ✅ 초기 비밀번호 직접 설정
|
|
'roleNames' => $roles ?: null,
|
|
'userTenants' => $tenants ?: null,
|
|
'customAttributes' => $custom,
|
|
], fn($v)=>$v!==null);
|
|
|
|
// 1) 생성
|
|
[$code, $body, $err] = mgmt_api('user/create', $payload, $PROJECT_ID, $MGMT_KEY);
|
|
|
|
if ($code === 200 || $code === 201) {
|
|
$created++;
|
|
usleep(200000); // 0.2초 대기 후 상태 갱신
|
|
// ✅ 생성 직후 강제 활성화 (대시보드 표시 즉시 반영)
|
|
mgmt_api('user/update/status', [
|
|
'loginId' => $loginId,
|
|
'status' => 'activated'
|
|
], $PROJECT_ID, $MGMT_KEY);
|
|
} elseif ($code === 409) {
|
|
// 2) 이미 존재 시 업데이트
|
|
[$uCode, $uBody, $uErr] = mgmt_api('user/update', $payload, $PROJECT_ID, $MGMT_KEY);
|
|
if ($uCode === 200) $updated++;
|
|
else {
|
|
$failed++; $errors[]=['row'=>$i+2,'loginId'=>$loginId,'error'=>"update:$uCode $uBody"];
|
|
continue;
|
|
}
|
|
} else {
|
|
$failed++; $errors[]=['row'=>$i+2,'loginId'=>$loginId,'error'=>"create:$code $body"];
|
|
continue;
|
|
}
|
|
|
|
// 3) 비밀번호 설정(new_password)
|
|
if ($newPassword !== '') {
|
|
[$pwCode, $pwBody, $pwErr] = update_password($PROJECT_ID, $MGMT_KEY, $email, $newPassword);
|
|
if ($pwCode !== 200) {
|
|
$errors[]=['row'=>$i+2,'loginId'=>$loginId,'error'=>"비밀번호 변경 실패 ($pwCode)"];
|
|
}
|
|
}
|
|
|
|
// // 4) 재설정 이메일 발송 (옵션)
|
|
// if ($sendReset) {
|
|
// call_api("https://api.descope.com/v1/auth/password/reset/start", [
|
|
// CURLOPT_POST=>true,
|
|
// CURLOPT_HTTPHEADER=>[
|
|
// "x-descope-project-id: {$PROJECT_ID}",
|
|
// "Content-Type: application/json",
|
|
// ],
|
|
// CURLOPT_POSTFIELDS=> json_encode(["loginId"=>$loginId]),
|
|
// ]);
|
|
// }
|
|
|
|
usleep(120000); // rate-limit
|
|
}
|
|
|
|
// === 결과 출력
|
|
echo json_encode([
|
|
'status'=>'ok',
|
|
'created'=>$created,
|
|
'updated'=>$updated,
|
|
'failed'=>$failed,
|
|
'errors'=>$errors,
|
|
], JSON_UNESCAPED_UNICODE);
|