$v !== '' )); return $images; } if (isset($input['image']) && trim($input['image']) !== '') { return [trim($input['image'])]; } return []; } function normalize_stack($input) { if (isset($input['stack']) && is_array($input['stack'])) { return array_values(array_filter( array_map('trim', $input['stack']), fn($v) => $v !== '' )); } if (isset($input['stack']) && is_string($input['stack'])) { return array_values(array_filter( array_map('trim', explode(',', $input['stack'])), fn($v) => $v !== '' )); } return []; } // 기존 프로젝트 데이터를 새 형식으로 마이그레이션 function migrate_project_data($project) { if (!isset($project['images']) || !is_array($project['images'])) { $project['images'] = []; if (!empty($project['image'])) { $project['images'][] = $project['image']; } } $project['image'] = $project['images'][0] ?? ''; // 새 필드들 기본값 if (!isset($project['stack']) || !is_array($project['stack'])) $project['stack'] = []; if (!isset($project['period_start'])) $project['period_start'] = ''; if (!isset($project['period_end'])) $project['period_end'] = ''; if (!isset($project['video_url'])) $project['video_url'] = ''; // 기존 demo_url 필드는 제거 (마이그레이션) unset($project['demo_url']); return $project; } // ===================================================== // GET: 프로젝트 목록 조회 // ===================================================== if ($method === 'GET') { $projects = read_json_safe(PROJECTS_FILE); if ($projects === null) { json_response(['error' => '데이터를 읽을 수 없습니다'], 500); } $projects = array_map('migrate_project_data', $projects); usort($projects, function($a, $b) { return ($b['id'] ?? 0) - ($a['id'] ?? 0); }); json_response($projects); } require_auth(); // ===================================================== // POST: 새 프로젝트 추가 // ===================================================== if ($method === 'POST') { $input = get_json_input(); $title = trim($input['title'] ?? ''); $label = trim($input['label'] ?? ''); $description = trim($input['description'] ?? ''); if (empty($title) || empty($label) || empty($description)) { json_response(['error' => '제목, 라벨, 설명은 필수입니다'], 400); } $projects = read_json_safe(PROJECTS_FILE) ?? []; $maxId = 0; foreach ($projects as $p) { if (($p['id'] ?? 0) > $maxId) $maxId = $p['id']; } $images = normalize_images($input); $stack = normalize_stack($input); $newProject = [ 'id' => $maxId + 1, 'title' => $title, 'label' => $label, 'description' => $description, 'icon' => trim($input['icon'] ?? 'fa-solid fa-code'), 'images' => $images, 'image' => $images[0] ?? '', 'link' => trim($input['link'] ?? ''), 'stack' => $stack, 'period_start' => trim($input['period_start'] ?? ''), 'period_end' => trim($input['period_end'] ?? ''), 'video_url' => trim($input['video_url'] ?? ''), 'created_at' => date('Y-m-d') ]; $projects[] = $newProject; if (write_json_safe(PROJECTS_FILE, $projects)) { json_response(['success' => true, 'project' => $newProject]); } else { json_response(['error' => '저장에 실패했습니다'], 500); } } // ===================================================== // PUT: 프로젝트 수정 // ===================================================== if ($method === 'PUT') { $input = get_json_input(); $id = intval($input['id'] ?? 0); if ($id <= 0) { json_response(['error' => '유효하지 않은 ID'], 400); } $projects = read_json_safe(PROJECTS_FILE) ?? []; $found = false; foreach ($projects as $key => $project) { if ($project['id'] === $id) { $projects[$key]['title'] = trim($input['title'] ?? $project['title']); $projects[$key]['label'] = trim($input['label'] ?? $project['label']); $projects[$key]['description'] = trim($input['description'] ?? $project['description']); $projects[$key]['icon'] = trim($input['icon'] ?? $project['icon']); $projects[$key]['link'] = trim($input['link'] ?? $project['link']); if (isset($input['images']) || isset($input['image'])) { $images = normalize_images($input); $projects[$key]['images'] = $images; $projects[$key]['image'] = $images[0] ?? ''; } if (isset($input['stack'])) { $projects[$key]['stack'] = normalize_stack($input); } if (isset($input['period_start'])) { $projects[$key]['period_start'] = trim($input['period_start']); } if (isset($input['period_end'])) { $projects[$key]['period_end'] = trim($input['period_end']); } if (isset($input['video_url'])) { $projects[$key]['video_url'] = trim($input['video_url']); } // 기존 demo_url 필드 제거 unset($projects[$key]['demo_url']); $projects[$key]['updated_at'] = date('Y-m-d'); $found = true; break; } } if (!$found) { json_response(['error' => '프로젝트를 찾을 수 없습니다'], 404); } if (write_json_safe(PROJECTS_FILE, $projects)) { json_response(['success' => true]); } else { json_response(['error' => '저장에 실패했습니다'], 500); } } // ===================================================== // DELETE: 프로젝트 삭제 // ===================================================== if ($method === 'DELETE') { $id = intval($_GET['id'] ?? 0); if ($id <= 0) { json_response(['error' => '유효하지 않은 ID'], 400); } $projects = read_json_safe(PROJECTS_FILE) ?? []; $filtered = array_values(array_filter($projects, function($p) use ($id) { return ($p['id'] ?? 0) !== $id; })); if (count($filtered) === count($projects)) { json_response(['error' => '프로젝트를 찾을 수 없습니다'], 404); } if (write_json_safe(PROJECTS_FILE, $filtered)) { json_response(['success' => true]); } else { json_response(['error' => '삭제에 실패했습니다'], 500); } } json_response(['error' => 'Method not allowed'], 405);