Files

269 lines
9.2 KiB
PHP
Raw Permalink Normal View History

2026-05-31 21:05:59 +09:00
<?php
require_once 'config.php';
require_once __DIR__ . '/error_config.php';
2026-05-31 22:23:51 +09:00
start_secure_session();
2026-05-31 21:05:59 +09:00
set_json_headers();
define('LEARNING_FILE', DATA_DIR . '/learning.json');
$method = $_SERVER['REQUEST_METHOD'];
2026-05-31 22:23:51 +09:00
function clean_learning_content($content) {
$content = is_string($content) ? trim($content) : '';
$content = preg_replace_callback('/\]\(([^)]+)\)/', function($m) {
$url = trim($m[1]);
return is_safe_public_url($url) ? '](' . $url . ')' : '](#)';
}, $content);
$content = preg_replace_callback('/@video(\[[^\]]*\])?\(([^)]+)\)/', function($m) {
$attrs = $m[1] ?? '';
$url = trim($m[2]);
return is_safe_public_url($url) ? '@video' . $attrs . '(' . $url . ')' : '';
}, $content);
$content = preg_replace_callback('/\{color:([^}]+)\}([\s\S]+?)\{\/color\}/', function($m) {
$color = clean_css_color($m[1], '');
return $color === '' ? $m[2] : '{color:' . $color . '}' . $m[2] . '{/color}';
}, $content);
return $content;
}
2026-05-31 21:05:59 +09:00
// =====================================================
// GET: 학습 일지 목록 또는 단일 글 (인증 불필요)
// =====================================================
if ($method === 'GET') {
$learnings = read_json_safe(LEARNING_FILE) ?? [];
// 단일 글 조회: ?id=N
if (isset($_GET['id'])) {
$id = intval($_GET['id']);
foreach ($learnings as $l) {
if (($l['id'] ?? 0) === $id) {
// is_learning 필드는 더이상 의미 없음 (제거)
unset($l['is_learning']);
json_response($l);
}
}
json_response(['error' => '글을 찾을 수 없습니다'], 404);
}
// is_learning 필드 마이그레이션 (제거)
foreach ($learnings as &$l) {
unset($l['is_learning']);
}
unset($l);
// 카테고리 필터: ?category_id=N (단일 또는 카테고리 + 모든 자식)
if (isset($_GET['category_id'])) {
$catId = intval($_GET['category_id']);
$categories = read_json_safe(DATA_DIR . '/categories.json') ?? [];
// 자식 카테고리 ID도 포함
$catIds = [$catId];
foreach ($categories as $c) {
if (($c['parent_id'] ?? null) === $catId) {
$catIds[] = $c['id'];
}
}
$learnings = array_values(array_filter($learnings, function($l) use ($catIds) {
return in_array(($l['category_id'] ?? 0), $catIds);
}));
}
// 최신순 정렬
usort($learnings, function($a, $b) {
$aDate = $a['created_at'] ?? '';
$bDate = $b['created_at'] ?? '';
if ($aDate !== $bDate) return strcmp($bDate, $aDate);
return ($b['id'] ?? 0) - ($a['id'] ?? 0);
});
json_response($learnings);
}
require_auth();
2026-05-31 22:23:51 +09:00
require_csrf();
2026-05-31 21:05:59 +09:00
// =====================================================
// POST: 새 학습 일지 작성
// =====================================================
if ($method === 'POST') {
$input = get_json_input();
$title = trim($input['title'] ?? '');
2026-05-31 22:23:51 +09:00
$content = clean_learning_content($input['content'] ?? '');
2026-05-31 21:05:59 +09:00
$categoryId = intval($input['category_id'] ?? 0);
if (empty($title) || empty($content)) {
json_response(['error' => '제목과 내용은 필수입니다'], 400);
}
if ($categoryId <= 0) {
json_response(['error' => '카테고리를 선택해주세요'], 400);
}
// 카테고리가 서브 카테고리(parent_id 있음)인지 확인 - 글은 서브 카테고리에만 속해야 함
$categories = read_json_safe(DATA_DIR . '/categories.json') ?? [];
$foundCat = null;
foreach ($categories as $c) {
if (($c['id'] ?? 0) === $categoryId) {
$foundCat = $c;
break;
}
}
if (!$foundCat) {
json_response(['error' => '카테고리를 찾을 수 없습니다'], 400);
}
if (empty($foundCat['parent_id'])) {
json_response(['error' => '글은 서브 카테고리에만 작성할 수 있습니다 (예: Unity > 학습)'], 400);
}
// 태그 처리
$tags = [];
if (isset($input['tags'])) {
if (is_array($input['tags'])) {
$tags = array_values(array_filter(
array_map('trim', $input['tags']),
fn($v) => $v !== ''
));
} elseif (is_string($input['tags'])) {
$tags = array_values(array_filter(
array_map('trim', explode(',', $input['tags'])),
fn($v) => $v !== ''
));
}
}
$learnings = read_json_safe(LEARNING_FILE) ?? [];
$maxId = 0;
foreach ($learnings as $l) {
if (($l['id'] ?? 0) > $maxId) $maxId = $l['id'];
}
$createdAt = trim($input['created_at'] ?? '');
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdAt)) {
$createdAt = date('Y-m-d');
}
$newLearning = [
'id' => $maxId + 1,
'title' => $title,
'category_id' => $categoryId,
'tags' => $tags,
'content' => $content,
'created_at' => $createdAt,
'updated_at' => date('Y-m-d')
];
$learnings[] = $newLearning;
if (write_json_safe(LEARNING_FILE, $learnings)) {
json_response(['success' => true, 'learning' => $newLearning]);
} 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);
}
$learnings = read_json_safe(LEARNING_FILE) ?? [];
$found = false;
foreach ($learnings as $key => $l) {
if ($l['id'] === $id) {
// is_learning 필드는 제거
unset($learnings[$key]['is_learning']);
if (isset($input['title'])) {
$title = trim($input['title']);
if ($title === '') json_response(['error' => '제목은 비울 수 없습니다'], 400);
$learnings[$key]['title'] = $title;
}
if (isset($input['content'])) {
2026-05-31 22:23:51 +09:00
$content = clean_learning_content($input['content']);
2026-05-31 21:05:59 +09:00
if ($content === '') json_response(['error' => '내용은 비울 수 없습니다'], 400);
$learnings[$key]['content'] = $content;
}
if (isset($input['category_id'])) {
$catId = intval($input['category_id']);
if ($catId > 0) {
// 서브 카테고리 검증
$categories = read_json_safe(DATA_DIR . '/categories.json') ?? [];
$foundCat = null;
foreach ($categories as $c) {
if (($c['id'] ?? 0) === $catId) { $foundCat = $c; break; }
}
if ($foundCat && empty($foundCat['parent_id'])) {
json_response(['error' => '글은 서브 카테고리에만 속할 수 있습니다'], 400);
}
$learnings[$key]['category_id'] = $catId;
}
}
if (isset($input['tags'])) {
$tags = is_array($input['tags'])
? array_values(array_filter(array_map('trim', $input['tags']), fn($v) => $v !== ''))
: (is_string($input['tags'])
? array_values(array_filter(array_map('trim', explode(',', $input['tags'])), fn($v) => $v !== ''))
: []);
$learnings[$key]['tags'] = $tags;
}
if (isset($input['created_at'])) {
$createdAt = trim($input['created_at']);
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdAt)) {
$learnings[$key]['created_at'] = $createdAt;
}
}
$learnings[$key]['updated_at'] = date('Y-m-d');
$found = true;
break;
}
}
if (!$found) {
json_response(['error' => '글을 찾을 수 없습니다'], 404);
}
if (write_json_safe(LEARNING_FILE, $learnings)) {
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);
}
$learnings = read_json_safe(LEARNING_FILE) ?? [];
$filtered = array_values(array_filter($learnings, function($l) use ($id) {
return ($l['id'] ?? 0) !== $id;
}));
if (count($filtered) === count($learnings)) {
json_response(['error' => '글을 찾을 수 없습니다'], 404);
}
if (write_json_safe(LEARNING_FILE, $filtered)) {
json_response(['success' => true]);
} else {
json_response(['error' => '삭제에 실패했습니다'], 500);
}
}
json_response(['error' => 'Method not allowed'], 405);