211 lines
7.5 KiB
PHP
211 lines
7.5 KiB
PHP
|
|
<?php
|
||
|
|
require_once 'config.php';
|
||
|
|
require_once __DIR__ . '/error_config.php';
|
||
|
|
session_start();
|
||
|
|
set_json_headers();
|
||
|
|
|
||
|
|
define('CATEGORIES_FILE', DATA_DIR . '/categories.json');
|
||
|
|
|
||
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
||
|
|
|
||
|
|
// 카테고리 정규화 (parent_id 필드 보장)
|
||
|
|
function normalize_category($cat) {
|
||
|
|
if (!isset($cat['parent_id'])) $cat['parent_id'] = null;
|
||
|
|
if (!isset($cat['order'])) $cat['order'] = 0;
|
||
|
|
if (!isset($cat['color'])) $cat['color'] = '#00f2ff';
|
||
|
|
return $cat;
|
||
|
|
}
|
||
|
|
|
||
|
|
// =====================================================
|
||
|
|
// GET: 카테고리 목록
|
||
|
|
// =====================================================
|
||
|
|
if ($method === 'GET') {
|
||
|
|
$categories = read_json_safe(CATEGORIES_FILE) ?? [];
|
||
|
|
$categories = array_map('normalize_category', $categories);
|
||
|
|
|
||
|
|
usort($categories, function($a, $b) {
|
||
|
|
// parent끼리 먼저 정렬되도록
|
||
|
|
$aParent = $a['parent_id'] ?? 0;
|
||
|
|
$bParent = $b['parent_id'] ?? 0;
|
||
|
|
if ($aParent !== $bParent) return $aParent - $bParent;
|
||
|
|
return ($a['order'] ?? 0) - ($b['order'] ?? 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
json_response($categories);
|
||
|
|
}
|
||
|
|
|
||
|
|
require_auth();
|
||
|
|
|
||
|
|
// =====================================================
|
||
|
|
// POST: 카테고리 추가
|
||
|
|
// =====================================================
|
||
|
|
if ($method === 'POST') {
|
||
|
|
$input = get_json_input();
|
||
|
|
$name = trim($input['name'] ?? '');
|
||
|
|
$color = trim($input['color'] ?? '#00f2ff');
|
||
|
|
$parentId = isset($input['parent_id']) && $input['parent_id'] !== '' && $input['parent_id'] !== null
|
||
|
|
? intval($input['parent_id'])
|
||
|
|
: null;
|
||
|
|
|
||
|
|
if (empty($name)) {
|
||
|
|
json_response(['error' => '카테고리 이름은 필수입니다'], 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
$categories = read_json_safe(CATEGORIES_FILE) ?? [];
|
||
|
|
|
||
|
|
// parent_id 유효성 검증
|
||
|
|
if ($parentId !== null) {
|
||
|
|
$parentFound = false;
|
||
|
|
foreach ($categories as $c) {
|
||
|
|
if (($c['id'] ?? 0) === $parentId) {
|
||
|
|
// 부모가 또 다른 부모를 가지면 안 됨 (2단계로 제한)
|
||
|
|
if (!empty($c['parent_id'])) {
|
||
|
|
json_response(['error' => '서브 카테고리 아래에는 카테고리를 만들 수 없습니다'], 400);
|
||
|
|
}
|
||
|
|
$parentFound = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!$parentFound) {
|
||
|
|
json_response(['error' => '부모 카테고리를 찾을 수 없습니다'], 400);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$maxId = 0;
|
||
|
|
$maxOrder = 0;
|
||
|
|
foreach ($categories as $c) {
|
||
|
|
if (($c['id'] ?? 0) > $maxId) $maxId = $c['id'];
|
||
|
|
// 같은 부모 내에서 max order
|
||
|
|
if (($c['parent_id'] ?? null) === $parentId && ($c['order'] ?? 0) > $maxOrder) {
|
||
|
|
$maxOrder = $c['order'];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$newCategory = [
|
||
|
|
'id' => $maxId + 1,
|
||
|
|
'name' => $name,
|
||
|
|
'color' => $color,
|
||
|
|
'parent_id' => $parentId,
|
||
|
|
'order' => $maxOrder + 1
|
||
|
|
];
|
||
|
|
|
||
|
|
$categories[] = $newCategory;
|
||
|
|
|
||
|
|
if (write_json_safe(CATEGORIES_FILE, $categories)) {
|
||
|
|
json_response(['success' => true, 'category' => $newCategory]);
|
||
|
|
} 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);
|
||
|
|
}
|
||
|
|
|
||
|
|
$categories = read_json_safe(CATEGORIES_FILE) ?? [];
|
||
|
|
$found = false;
|
||
|
|
|
||
|
|
foreach ($categories as $key => $cat) {
|
||
|
|
if ($cat['id'] === $id) {
|
||
|
|
if (isset($input['name'])) $categories[$key]['name'] = trim($input['name']);
|
||
|
|
if (isset($input['color'])) $categories[$key]['color'] = trim($input['color']);
|
||
|
|
if (isset($input['order'])) $categories[$key]['order'] = intval($input['order']);
|
||
|
|
// parent_id 변경은 허용하되, 자식이 있는 경우 자식으로 만들지 못하게
|
||
|
|
if (array_key_exists('parent_id', $input)) {
|
||
|
|
$newParent = $input['parent_id'];
|
||
|
|
$newParent = ($newParent === '' || $newParent === null) ? null : intval($newParent);
|
||
|
|
|
||
|
|
// 자기 자신을 부모로 설정 불가
|
||
|
|
if ($newParent === $id) {
|
||
|
|
json_response(['error' => '자기 자신을 부모로 설정할 수 없습니다'], 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 자식이 있는 카테고리는 다른 카테고리의 자식이 될 수 없음
|
||
|
|
$hasChildren = false;
|
||
|
|
foreach ($categories as $c) {
|
||
|
|
if (($c['parent_id'] ?? null) === $id) {
|
||
|
|
$hasChildren = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($newParent !== null && $hasChildren) {
|
||
|
|
json_response(['error' => '하위 카테고리가 있는 카테고리는 다른 카테고리의 하위로 이동할 수 없습니다'], 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
$categories[$key]['parent_id'] = $newParent;
|
||
|
|
}
|
||
|
|
$found = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!$found) {
|
||
|
|
json_response(['error' => '카테고리를 찾을 수 없습니다'], 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (write_json_safe(CATEGORIES_FILE, $categories)) {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
$categories = read_json_safe(CATEGORIES_FILE) ?? [];
|
||
|
|
|
||
|
|
// 자식 카테고리 ID 수집
|
||
|
|
$allIdsToDelete = [$id];
|
||
|
|
foreach ($categories as $c) {
|
||
|
|
if (($c['parent_id'] ?? null) === $id) {
|
||
|
|
$allIdsToDelete[] = $c['id'];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 해당 카테고리(들)을 사용하는 글이 있는지 확인
|
||
|
|
$learnings = read_json_safe(DATA_DIR . '/learning.json') ?? [];
|
||
|
|
$usedCount = 0;
|
||
|
|
foreach ($learnings as $l) {
|
||
|
|
if (in_array(($l['category_id'] ?? 0), $allIdsToDelete)) $usedCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($usedCount > 0) {
|
||
|
|
$isParent = count($allIdsToDelete) > 1;
|
||
|
|
$msg = $isParent
|
||
|
|
? "이 카테고리(또는 하위 카테고리)를 사용하는 글이 {$usedCount}개 있습니다. 먼저 해당 글들을 다른 카테고리로 옮기거나 삭제해주세요."
|
||
|
|
: "이 카테고리를 사용하는 글이 {$usedCount}개 있습니다. 먼저 해당 글들을 다른 카테고리로 옮기거나 삭제해주세요.";
|
||
|
|
json_response(['error' => $msg], 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
$filtered = array_values(array_filter($categories, function($c) use ($allIdsToDelete) {
|
||
|
|
return !in_array(($c['id'] ?? 0), $allIdsToDelete);
|
||
|
|
}));
|
||
|
|
|
||
|
|
if (count($filtered) === count($categories)) {
|
||
|
|
json_response(['error' => '카테고리를 찾을 수 없습니다'], 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (write_json_safe(CATEGORIES_FILE, $filtered)) {
|
||
|
|
json_response(['success' => true, 'deleted_count' => count($allIdsToDelete)]);
|
||
|
|
} else {
|
||
|
|
json_response(['error' => '삭제에 실패했습니다'], 500);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
json_response(['error' => 'Method not allowed'], 405);
|