feat: add SMS diagnostics admin check

This commit is contained in:
unknown
2026-07-16 16:23:12 +09:00
parent ae1f30592d
commit 9c4dac9a70
7 changed files with 146 additions and 2 deletions

View File

@@ -5,6 +5,7 @@ import ExcelJS from 'exceljs';
import { ok } from '../http/apiResponse.js';
import { ApiError } from '../http/errors.js';
import { query } from '../db/pool.js';
import { diagnoseSmsConnectivity } from '../services/sms.js';
import { parseRole, parseRoles, requireAdmin, requireCurrentUser, type QueryFn, type UserRow } from './security.js';
type Row = Record<string, unknown>;
@@ -469,6 +470,11 @@ export function createAdminRouter(deps: AdminRouterDeps = { query }): Router {
ok(res, await watcher1(dbQuery));
}));
router.post('/sms/diagnostics', asyncRoute(async (req, res) => {
await requireAdmin(dbQuery, req);
ok(res, await diagnoseSmsConnectivity());
}));
router.get('/teams', asyncRoute(async (req, res) => {
await requireAdmin(dbQuery, req);
ok(res, await listTeams(dbQuery));

View File

@@ -12,6 +12,17 @@ export interface SmsMessage {
text: string;
}
export interface SmsDiagnosticsResult {
provider: string;
url: string;
ok: boolean;
reachable: boolean;
httpStatus: number | null;
elapsedMs: number;
checkedAt: string;
error: string | null;
}
type SmsApiResponse = {
requestId?: string;
requestTime?: string;
@@ -120,6 +131,62 @@ function resolveHanbankSmsUrl(): string {
return `${baseUrl.replace(/\/+$/, '')}/sens/sms`;
}
export async function diagnoseSmsConnectivity(): Promise<SmsDiagnosticsResult> {
const provider = smsProvider();
const url = provider.toLowerCase() === 'hanbank' || provider.toLowerCase() === 'bok'
? resolveHanbankSmsUrl()
: smsApiUrl() ?? '';
const startedAt = Date.now();
if (!url) {
return {
provider,
url,
ok: false,
reachable: false,
httpStatus: null,
elapsedMs: 0,
checkedAt: new Date().toISOString(),
error: 'ACS_SMS_API_URL이 설정되지 않았습니다.',
};
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), smsTimeoutMs());
try {
const response = await fetch(url, {
method: 'GET',
headers: { accept: 'application/json' },
signal: controller.signal,
});
return {
provider,
url,
ok: true,
reachable: true,
httpStatus: response.status,
elapsedMs: Date.now() - startedAt,
checkedAt: new Date().toISOString(),
error: null,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
provider,
url,
ok: false,
reachable: false,
httpStatus: null,
elapsedMs: Date.now() - startedAt,
checkedAt: new Date().toISOString(),
error: message.slice(0, 500),
};
} finally {
clearTimeout(timeout);
}
}
function isSuccessResponse(response: SmsApiResponse): boolean {
const statusCode = String(response.statusCode ?? '').trim();
const statusName = String(response.statusName ?? '').trim().toLowerCase();