Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 104 additions & 7 deletions src/app/result/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,21 +339,118 @@ export default function CheckTimeApp() {
setAlarmData(data);
};

const handleSubmit = async (url: string) => {
// URL 형식인지 확인하는 함수
const isValidUrl = (str: string) => {
try {
new URL(str);
return true;
} catch {
return str.startsWith('http://') || str.startsWith('https://');
}
};

// Site 인터페이스
interface Site {
id: number;
url: string;
name: string;
category: string;
description?: string;
keywords: string[];
usage_count: number;
optimal_offset: number;
average_rtt: number;
success_rate: number;
}

// 키워드로 사이트 검색해서 첫 번째 결과의 URL 가져오기
const getUrlFromKeyword = async (keyword: string): Promise<string | null> => {
try {
console.log(`키워드 검색 시작: "${keyword}"`);

const response = await fetch(
`http://localhost:3001/api/sites?search=${encodeURIComponent(
keyword.trim(),
)}&limit=5`,
);
const data = await response.json();

Comment on lines +371 to +377
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

클라이언트에서 localhost 절대 경로 사용 — 배포/HTTPS에서 실패합니다

http://localhost:3001 고정 호출은 배포 환경에서 실패(도메인 불일치)하고, HTTPS 페이지에서는 Mixed Content 차단됩니다. 같은 오리진의 Next API 라우트를 프록시로 쓰거나 환경변수를 사용하세요. 또한 response.ok 체크가 없어 4xx/5xx에서 JSON 파싱 에러 가능성이 있습니다.

-      const response = await fetch(
-        `http://localhost:3001/api/sites?search=${encodeURIComponent(
-          keyword.trim(),
-        )}&limit=5`,
-      );
+      const response = await fetch(
+        `/api/sites?search=${encodeURIComponent(keyword.trim())}&limit=5`,
+      );
+      if (!response.ok) {
+        console.error(`검색 API 오류: ${response.status} ${response.statusText}`);
+        return null;
+      }
       const data = await response.json();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await fetch(
`http://localhost:3001/api/sites?search=${encodeURIComponent(
keyword.trim(),
)}&limit=5`,
);
const data = await response.json();
const response = await fetch(
`/api/sites?search=${encodeURIComponent(keyword.trim())}&limit=5`,
);
if (!response.ok) {
console.error(`검색 API 오류: ${response.status} ${response.statusText}`);
return null;
}
const data = await response.json();
🤖 Prompt for AI Agents
In src/app/result/page.tsx around lines 371 to 377, the code is calling a
hard-coded http://localhost:3001 which will fail in deployed or HTTPS
environments and also doesn't check response.ok; change the fetch to use a
same-origin Next.js API route (e.g. /api/sites?search=...) or build the base URL
from an environment variable (NEXT_PUBLIC_API_URL) so it works in
production/HTTPS, and add a response.ok check before calling response.json() to
throw or handle HTTP errors (and handle exceptions from fetch/json parsing).

console.log('검색 API 응답:', data);

if (data.success && data.data.sites && data.data.sites.length > 0) {
const sites: Site[] = data.data.sites;

// 모든 검색 결과 로그 출력
console.log(`"${keyword}" 검색 결과 (${sites.length}개):`);
sites.forEach((site: Site, index: number) => {
console.log(` ${index + 1}. ${site.name} - ${site.url}`);
console.log(` 키워드: [${site.keywords?.join(', ') || '없음'}]`);
});

// 키워드와 정확히 매치되는 사이트 찾기
const exactMatch = sites.find((site: Site) =>
site.keywords?.some(
(kw: string) => kw.toLowerCase() === keyword.toLowerCase(),
),
);

if (exactMatch) {
console.log(`정확 매치 발견: ${exactMatch.name} - ${exactMatch.url}`);
return exactMatch.url;
}

// 정확 매치가 없으면 첫 번째 결과 사용
const firstResult = sites[0];
console.log(
`정확 매치 없음. 첫 번째 결과 사용: ${firstResult.name} - ${firstResult.url}`,
);
return firstResult.url;
}

console.log(`"${keyword}" 검색 결과 없음`);
return null;
} catch (error) {
console.error('사이트 검색 실패:', error);
return null;
}
};

const handleSubmit = async (input: string) => {
setIsLoading(true);
setServerTimeData(null);

try {
const startTime = Date.now(); // 클라이언트 요청 시작 시간 기록
const clientTimeAtRequest = new Date().toISOString(); // 요청 시점의 클라이언트 시간

let finalUrl = input.trim();

// URL 형식이 아니면 키워드로 검색
if (!isValidUrl(finalUrl)) {
console.log(`키워드 검색 시작: "${finalUrl}"`);
const foundUrl = await getUrlFromKeyword(finalUrl);

if (foundUrl) {
finalUrl = foundUrl;
console.log(`검색 성공: ${input} → ${finalUrl}`);
} else {
setServerTimeData({
url: input,
clientTime: clientTimeAtRequest,
error: `"${input}"에 대한 검색 결과가 없습니다.\n\n사용 가능한 키워드 예시:\n- 숭실대, SSU, 수강신청\n- 인터파크, 티켓, 콘서트\n- 무신사, 쇼핑, 패션\n- 예스24, 책, 도서\n- 지마켓, 쇼핑몰`,
});
setIsLoading(false);
return;
}
}

// 1. /api/time/compare 엔드포인트 호출
const compareResponse = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE}/api/time/compare`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetUrl: url }),
body: JSON.stringify({ targetUrl: finalUrl }),
},
);

Expand All @@ -367,7 +464,7 @@ export default function CheckTimeApp() {

// api/time/compare 응답에서 필요한 데이터 추출
setServerTimeData({
url,
url: finalUrl,
clientTime: clientTimeAtRequest, // 클라이언트 요청 시작 시의 시간
serverTime: apiData.timeComparison?.correctedTargetTime, // 보정된 타겟 서버 시간
timeDifference: apiData.timeComparison?.timeDifference, // 우리 서버 시간 - 타겟 서버 시간 (백엔드에서 계산된 값)
Expand All @@ -383,22 +480,22 @@ export default function CheckTimeApp() {
});
} else {
setServerTimeData({
url,
url: finalUrl,
clientTime: clientTimeAtRequest,
error: result.error || '서버 시간 비교 실패',
});
}
} else {
setServerTimeData({
url,
url: finalUrl,
clientTime: clientTimeAtRequest,
error: `API 통신 오류: ${compareResponse.status} ${compareResponse.statusText}`,
});
}
} catch (error: unknown) {
console.error('서버 시간 확인 실패:', error);
setServerTimeData({
url,
url: input,
clientTime: new Date().toISOString(),
error:
error instanceof Error
Expand Down Expand Up @@ -445,7 +542,7 @@ export default function CheckTimeApp() {

{/* 서버 시간 검색 폼 */}
<div className="mt-4 flex justify-center mb-4">
<ServerSearchForm onSubmit={(url) => handleSubmit(url)} />
<ServerSearchForm onSubmit={(input) => handleSubmit(input)} />
</div>

<hr className="my-4 border-t border-gray-300 w-full max-w-4xl mx-auto" />
Expand Down