refactor: improve OpenAI service and tools

- Enhance OpenAI message types with tool_calls support
- Improve security validation and rate limiting
- Update utility tools and weather tool
- Minor fixes in deposit-agent and domain-register

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-28 20:26:31 +09:00
parent 7ef0ec7594
commit e32e3c6a44
7 changed files with 238 additions and 94 deletions

View File

@@ -21,6 +21,11 @@ const NameserverResponseSchema = z.object({
nameservers: z.array(z.string()).optional(),
});
const PriceResponseSchema = z.object({
krw: z.number().optional(),
register_krw: z.number().optional(),
});
interface RegisterResult {
success: boolean;
domain?: string;
@@ -47,21 +52,68 @@ export async function executeDomainRegister(
}
try {
// 1. 현재 잔액 확인
// 1. Verify price from Namecheap API (security: prevent price manipulation)
const domainTld = domain.split('.').pop() || '';
const priceCheckResponse = await fetch(`${apiUrl}/prices/${domainTld}`, {
headers: { 'X-API-Key': apiKey }
});
if (!priceCheckResponse.ok) {
logger.error('Failed to fetch price from Namecheap API', new Error(`HTTP ${priceCheckResponse.status}`));
return { success: false, error: '가격 정보를 가져올 수 없습니다.' };
}
const priceJsonData = await priceCheckResponse.json();
const priceParseResult = PriceResponseSchema.safeParse(priceJsonData);
if (!priceParseResult.success) {
logger.error('Price response schema validation failed', priceParseResult.error);
return { success: false, error: '가격 정보 형식이 올바르지 않습니다.' };
}
const priceData = priceParseResult.data;
const actualPrice = priceData.krw || priceData.register_krw;
if (!actualPrice || typeof actualPrice !== 'number') {
logger.error('Invalid price data from API', new Error('Missing or invalid krw/register_krw'), { priceData });
return { success: false, error: '가격 정보가 올바르지 않습니다.' };
}
// SECURITY: Verify callback price matches actual API price (allow 5% tolerance for exchange rate fluctuation)
const priceDiff = Math.abs(actualPrice - price);
const tolerance = actualPrice * 0.05; // 5%
if (priceDiff > tolerance) {
logger.warn('Price mismatch detected - potential price manipulation', {
callbackPrice: price,
actualPrice,
difference: priceDiff,
domain
});
return {
success: false,
error: `가격이 변경되었습니다. 현재 가격: ${actualPrice.toLocaleString()}\n다시 등록을 시도해주세요.`
};
}
logger.info('Price verification passed', { domain, callbackPrice: price, actualPrice });
// 2. 현재 잔액 확인
const balanceRow = await env.DB.prepare(
'SELECT balance FROM user_deposits WHERE user_id = ?'
).bind(userId).first<{ balance: number }>();
const currentBalance = balanceRow?.balance || 0;
if (currentBalance < price) {
// Use actual price from API instead of callback price
if (currentBalance < actualPrice) {
return {
success: false,
error: `잔액이 부족합니다. (현재: ${currentBalance.toLocaleString()}원, 필요: ${price.toLocaleString()}원)`
error: `잔액이 부족합니다. (현재: ${currentBalance.toLocaleString()}원, 필요: ${actualPrice.toLocaleString()}원)`
};
}
// 2. Namecheap API로 도메인 등록
console.log(`[DomainRegister] 도메인 등록 요청: ${domain}, 가격: ${price}`);
// 3. Namecheap API로 도메인 등록
logger.info('도메인 등록 요청', { domain, actualPrice, callbackPrice: price });
const registerResponse = await fetch(`${apiUrl}/domains/register`, {
method: 'POST',
@@ -88,13 +140,13 @@ export async function executeDomainRegister(
if (!registerResponse.ok || !registerResult.registered) {
const errorMsg = registerResult.error || registerResult.detail || '도메인 등록에 실패했습니다.';
console.error(`[DomainRegister] 등록 실패:`, registerResult);
logger.error('등록 실패', new Error(errorMsg), { registerResult });
return { success: false, error: errorMsg };
}
console.log(`[DomainRegister] 등록 성공:`, registerResult);
logger.info('등록 성공', { registerResult });
// 3. 잔액 차감 + 거래 기록 (Optimistic Locking)
// 4. 잔액 차감 + 거래 기록 (Optimistic Locking) - USE ACTUAL PRICE
try {
await executeWithOptimisticLock(env.DB, async () => {
// Read current balance and version
@@ -102,24 +154,24 @@ export async function executeDomainRegister(
'SELECT balance, version FROM user_deposits WHERE user_id = ?'
).bind(userId).first<{ balance: number; version: number }>();
if (!current || current.balance < price) {
if (!current || current.balance < actualPrice) {
throw new Error('잔액이 부족합니다.');
}
// Update balance with version check
// Update balance with version check - USE ACTUAL PRICE
const updateResult = await env.DB.prepare(
'UPDATE user_deposits SET balance = balance - ?, version = version + 1, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND version = ?'
).bind(price, userId, current.version).run();
).bind(actualPrice, userId, current.version).run();
if (!updateResult.success || updateResult.meta.changes === 0) {
throw new OptimisticLockError('Version mismatch on balance update');
}
// Insert transaction record
// Insert transaction record - USE ACTUAL PRICE
const txResult = await env.DB.prepare(
`INSERT INTO deposit_transactions (user_id, type, amount, status, description, confirmed_at)
VALUES (?, 'withdrawal', ?, 'confirmed', ?, CURRENT_TIMESTAMP)`
).bind(userId, price, `도메인 등록: ${domain}`).run();
).bind(userId, actualPrice, `도메인 등록: ${domain}`).run();
if (!txResult.success) {
throw new Error('거래 기록 생성 실패');
@@ -129,8 +181,9 @@ export async function executeDomainRegister(
userId,
telegramUserId,
domain,
price,
newBalance: current.balance - price,
actualPrice,
callbackPrice: price,
newBalance: current.balance - actualPrice,
});
});
} catch (error) {
@@ -139,7 +192,7 @@ export async function executeDomainRegister(
userId,
telegramUserId,
domain,
price,
actualPrice,
});
return {
success: false,
@@ -198,23 +251,30 @@ export async function executeDomainRegister(
}
}
} catch (infoError) {
console.log(`[DomainRegister] 도메인 정보 조회 실패 (무시):`, infoError);
logger.info('도메인 정보 조회 실패 (무시)', { error: infoError });
}
const newBalance = currentBalance - price;
console.log(`[DomainRegister] 완료: ${domain}, 잔액: ${currentBalance} -> ${newBalance}, 만료: ${expiresAt}, NS: ${nameservers.join(', ')}`);
const newBalance = currentBalance - actualPrice;
logger.info('도메인 등록 완료', {
domain,
oldBalance: currentBalance,
newBalance,
actualPrice,
expiresAt,
nameservers: nameservers.join(', ')
});
return {
success: true,
domain: domain,
price: price,
price: actualPrice, // Return actual price charged
newBalance: newBalance,
nameservers: nameservers,
expiresAt: expiresAt,
};
} catch (error) {
logger.error('도메인 등록 중 오류', error as Error, { domain, price });
logger.error('도메인 등록 중 오류', error as Error, { domain, callbackPrice: price });
return {
success: false,
error: '도메인 등록 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'