fix: critical P0+P1 issues for code quality score 9.0
P0 (Critical): - api.ts: Add transaction rollback on INSERT failure in /api/deposit/deduct - Restores balance if transaction record fails to insert - Logs rollback success/failure for audit trail - Maintains data consistency despite D1's non-transactional nature P1 (Important): - summary-service.ts: Replace double type assertions with Type Guards - Add D1BufferedMessageRow, D1SummaryRow interfaces - Add isBufferedMessageRow, isSummaryRow type guards - Runtime validation with compile-time type safety - Remove all `as unknown as` patterns - webhook.ts: Add integer range validation for callback queries - Add parseIntSafe() utility with min/max bounds - Validate domain registration price (0-10,000,000 KRW) - Prevent negative/overflow/NaN injection attacks - search-tool.ts: Implement KV caching for translation API - Cache Korean→English translations for 24 hours - Use RATE_LIMIT_KV namespace with 'translate:' prefix - Reduce redundant OpenAI API calls for repeated queries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,36 +65,59 @@ export async function executeSearchWeb(args: { query: string }, env?: Env): Prom
|
||||
|
||||
if (hasKorean && env?.OPENAI_API_KEY) {
|
||||
try {
|
||||
const translateRes = await retryWithBackoff(
|
||||
() => fetch(getOpenAIUrl(env), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `사용자의 검색어를 영문으로 번역하세요.
|
||||
// 번역 캐시 키 생성
|
||||
const cacheKey = `translate:${query}`;
|
||||
|
||||
// 1. 캐시 확인
|
||||
let usedCache = false;
|
||||
if (env.RATE_LIMIT_KV) {
|
||||
const cached = await env.RATE_LIMIT_KV.get(cacheKey);
|
||||
if (cached) {
|
||||
translatedQuery = cached;
|
||||
usedCache = true;
|
||||
logger.info('캐시된 번역 사용', { original: query, translated: cached });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 번역 API 호출 (캐시 미스인 경우만)
|
||||
if (!usedCache) {
|
||||
const translateRes = await retryWithBackoff(
|
||||
() => fetch(getOpenAIUrl(env), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `사용자의 검색어를 영문으로 번역하세요.
|
||||
- 외래어/기술용어는 원래 영문 표기로 변환 (예: 판골린→Pangolin, 도커→Docker)
|
||||
- 일반 한국어는 영문으로 번역
|
||||
- 검색에 최적화된 키워드로 변환
|
||||
- 번역된 검색어만 출력, 설명 없이`
|
||||
},
|
||||
{ role: 'user', content: query }
|
||||
],
|
||||
max_tokens: 100,
|
||||
temperature: 0.3,
|
||||
},
|
||||
{ role: 'user', content: query }
|
||||
],
|
||||
max_tokens: 100,
|
||||
temperature: 0.3,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
{ maxRetries: 2 } // 번역은 중요하지 않으므로 재시도 2회로 제한
|
||||
);
|
||||
if (translateRes.ok) {
|
||||
const translateData = await translateRes.json() as OpenAIResponse;
|
||||
translatedQuery = translateData.choices?.[0]?.message?.content?.trim() || query;
|
||||
logger.info('번역', { original: query, translated: translatedQuery });
|
||||
{ maxRetries: 2 } // 번역은 중요하지 않으므로 재시도 2회로 제한
|
||||
);
|
||||
if (translateRes.ok) {
|
||||
const translateData = await translateRes.json() as OpenAIResponse;
|
||||
translatedQuery = translateData.choices?.[0]?.message?.content?.trim() || query;
|
||||
logger.info('번역', { original: query, translated: translatedQuery });
|
||||
|
||||
// 3. 캐시 저장 (24시간 TTL)
|
||||
if (env.RATE_LIMIT_KV && translatedQuery !== query) {
|
||||
await env.RATE_LIMIT_KV.put(cacheKey, translatedQuery, { expirationTtl: 86400 });
|
||||
logger.info('번역 캐싱', { query, translatedQuery });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 번역 실패 시 원본 사용 (RetryError 포함)
|
||||
|
||||
Reference in New Issue
Block a user