181 lines
5.3 KiB
PHP
181 lines
5.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/common.php';
|
|
require_once dirname(__DIR__) . '/db_conn.php';
|
|
|
|
api_header_json();
|
|
|
|
$user = api_require_login();
|
|
$method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
|
|
|
/**
|
|
* Normalize keyword for storage (trim + max 50 chars).
|
|
*/
|
|
function normalize_keyword(string $value): string {
|
|
$keyword = trim($value);
|
|
if ($keyword === '') {
|
|
return '';
|
|
}
|
|
|
|
if (function_exists('mb_substr')) {
|
|
return mb_substr($keyword, 0, 50, 'UTF-8');
|
|
}
|
|
|
|
return substr($keyword, 0, 50);
|
|
}
|
|
|
|
/**
|
|
* Insert one search log row using per-user seq and short retry on PK conflicts.
|
|
*/
|
|
function insert_search_log(PDO $pdo, string $memberId, string $sysCompCode, string $keyword): array {
|
|
$maxAttempts = 3;
|
|
|
|
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
|
|
try {
|
|
$pdo->beginTransaction();
|
|
|
|
$stmtSeq = $pdo->prepare(
|
|
'SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq
|
|
FROM edu_search_logs
|
|
WHERE member_id = ?
|
|
AND sys_comp_code = ?'
|
|
);
|
|
$stmtSeq->execute([$memberId, $sysCompCode]);
|
|
$nextSeq = (int)($stmtSeq->fetchColumn() ?: 1);
|
|
|
|
$stmtInsert = $pdo->prepare(
|
|
'INSERT INTO edu_search_logs
|
|
(member_id, sys_comp_code, seq, keyword, searched_at)
|
|
VALUES
|
|
(?, ?, ?, ?, NOW())'
|
|
);
|
|
$stmtInsert->execute([$memberId, $sysCompCode, $nextSeq, $keyword]);
|
|
|
|
$pdo->commit();
|
|
|
|
return [
|
|
'seq' => $nextSeq,
|
|
'keyword' => $keyword,
|
|
'searched_at' => date('Y-m-d H:i:s'),
|
|
'retry_count' => $attempt - 1,
|
|
];
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
|
|
$sqlState = '';
|
|
if ($e instanceof PDOException && isset($e->errorInfo[0])) {
|
|
$sqlState = (string)$e->errorInfo[0];
|
|
}
|
|
|
|
$isDuplicateKey = ($sqlState === '23000');
|
|
if (!$isDuplicateKey || $attempt >= $maxAttempts) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new RuntimeException('search_log_insert_retry_exceeded');
|
|
}
|
|
|
|
if ($method === 'GET') {
|
|
$windowDays = 7;
|
|
$limit = (int)($_GET['limit'] ?? 20);
|
|
if ($limit < 1) {
|
|
$limit = 1;
|
|
} elseif ($limit > 50) {
|
|
$limit = 50;
|
|
}
|
|
|
|
try {
|
|
$pdo = db_conn();
|
|
|
|
$stmt = $pdo->prepare(
|
|
'SELECT seq, keyword, searched_at
|
|
FROM edu_search_logs
|
|
WHERE member_id = ?
|
|
AND sys_comp_code = ?
|
|
AND searched_at >= (NOW() - INTERVAL 7 DAY)
|
|
ORDER BY searched_at DESC, seq DESC
|
|
LIMIT ' . $limit
|
|
);
|
|
$stmt->execute([(string)$user['member_id'], (string)$user['sys_comp_code']]);
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
|
|
$logs = array_map(static function (array $row): array {
|
|
return [
|
|
'seq' => (int)($row['seq'] ?? 0),
|
|
'keyword' => (string)($row['keyword'] ?? ''),
|
|
'searched_at' => (string)($row['searched_at'] ?? ''),
|
|
];
|
|
}, $rows);
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'data' => [
|
|
'logs' => $logs,
|
|
'window_days' => $windowDays,
|
|
'limit' => $limit,
|
|
'member_id' => (string)$user['member_id'],
|
|
'sys_comp_code' => (string)$user['sys_comp_code'],
|
|
],
|
|
'meta' => [
|
|
'api' => 'search_logs',
|
|
'version' => 1,
|
|
'status' => 'ok',
|
|
],
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
error_log('[search_logs][GET] ' . $e->getMessage());
|
|
api_error(500, 'search_logs_fetch_failed');
|
|
}
|
|
}
|
|
|
|
if ($method === 'POST') {
|
|
$input = api_get_input();
|
|
$keyword = normalize_keyword((string)($input['keyword'] ?? ''));
|
|
|
|
if ($keyword === '') {
|
|
api_error(400, 'keyword_required');
|
|
}
|
|
|
|
try {
|
|
$pdo = db_conn();
|
|
$inserted = insert_search_log(
|
|
$pdo,
|
|
(string)$user['member_id'],
|
|
(string)$user['sys_comp_code'],
|
|
$keyword
|
|
);
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'data' => [
|
|
'accepted' => true,
|
|
'keyword' => $inserted['keyword'],
|
|
'seq' => (int)$inserted['seq'],
|
|
'searched_at' => (string)$inserted['searched_at'],
|
|
'member_id' => (string)$user['member_id'],
|
|
'sys_comp_code' => (string)$user['sys_comp_code'],
|
|
],
|
|
'meta' => [
|
|
'api' => 'search_logs',
|
|
'version' => 1,
|
|
'status' => 'ok',
|
|
'retry_count' => (int)$inserted['retry_count'],
|
|
],
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
error_log('[search_logs][POST] ' . $e->getMessage());
|
|
api_error(500, 'search_log_insert_failed');
|
|
}
|
|
}
|
|
|
|
api_error(405, 'method_not_allowed', [
|
|
'allowed_methods' => ['GET', 'POST'],
|
|
]);
|