dnsweeper
Version:
Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.
248 lines (218 loc) • 6.99 kB
text/typescript
import { Router, Request, Response } from 'express';
import { changeHistoryService } from '../services/changeHistoryService';
import { ChangeHistoryFilter } from '../types/history';
const router = Router();
/**
* 変更履歴一覧の取得
* GET /api/history
*/
router.get('/', async (req: Request, res: Response) => {
try {
const filter: ChangeHistoryFilter = {
domain: req.query.domain as string,
recordType: req.query.recordType as string,
changeType: req.query.changeType as string,
source: req.query.source as string,
userId: req.query.userId as string,
dateFrom: req.query.dateFrom ? new Date(req.query.dateFrom as string) : undefined,
dateTo: req.query.dateTo ? new Date(req.query.dateTo as string) : undefined,
limit: req.query.limit ? parseInt(req.query.limit as string, 10) : 20,
cursor: req.query.cursor as string,
sortBy: req.query.sortBy as any,
sortOrder: req.query.sortOrder as any
};
// 無効な日付をフィルター
if (filter.dateFrom && isNaN(filter.dateFrom.getTime())) {
filter.dateFrom = undefined;
}
if (filter.dateTo && isNaN(filter.dateTo.getTime())) {
filter.dateTo = undefined;
}
const history = await changeHistoryService.getChangeHistory(filter);
res.json({
success: true,
data: history
});
} catch (error) {
console.error('Error fetching change history:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: '変更履歴の取得に失敗しました'
});
}
});
/**
* 変更履歴統計の取得
* GET /api/history/stats
*/
router.get('/stats', async (req: Request, res: Response) => {
try {
const dateFrom = req.query.dateFrom
? new Date(req.query.dateFrom as string)
: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // デフォルト: 過去30日
const dateTo = req.query.dateTo
? new Date(req.query.dateTo as string)
: new Date(); // デフォルト: 現在
// 無効な日付のチェック
if (isNaN(dateFrom.getTime()) || isNaN(dateTo.getTime())) {
return res.status(400).json({
success: false,
error: 'Invalid date format',
message: '日付形式が正しくありません'
});
}
const stats = await changeHistoryService.getChangeStatistics(dateFrom, dateTo);
res.json({
success: true,
data: stats
});
} catch (error) {
console.error('Error fetching change statistics:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: '統計情報の取得に失敗しました'
});
}
});
/**
* 特定レコードの変更履歴取得
* GET /api/history/record/:recordId
*/
router.get('/record/:recordId', async (req: Request, res: Response) => {
try {
const { recordId } = req.params;
if (!recordId) {
return res.status(400).json({
success: false,
error: 'Missing recordId',
message: 'レコードIDが必要です'
});
}
const history = await changeHistoryService.getRecordHistory(recordId);
res.json({
success: true,
data: {
recordId,
changes: history,
totalCount: history.length
}
});
} catch (error) {
console.error('Error fetching record history:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'レコード履歴の取得に失敗しました'
});
}
});
/**
* 特定ドメインの変更履歴取得
* GET /api/history/domain/:domain
*/
router.get('/domain/:domain', async (req: Request, res: Response) => {
try {
const { domain } = req.params;
if (!domain) {
return res.status(400).json({
success: false,
error: 'Missing domain',
message: 'ドメイン名が必要です'
});
}
const history = await changeHistoryService.getDomainHistory(domain);
res.json({
success: true,
data: {
domain,
changes: history,
totalCount: history.length
}
});
} catch (error) {
console.error('Error fetching domain history:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'ドメイン履歴の取得に失敗しました'
});
}
});
/**
* 変更履歴の記録(内部API)
* POST /api/history/record
*/
router.post('/record', async (req: Request, res: Response) => {
try {
const changeData = req.body;
// 必須フィールドの検証
const requiredFields = ['recordId', 'domain', 'recordType', 'changeType', 'source'];
const missingFields = requiredFields.filter(field => !changeData[field]);
if (missingFields.length > 0) {
return res.status(400).json({
success: false,
error: 'Missing required fields',
message: `必須フィールドが不足しています: ${missingFields.join(', ')}`
});
}
const change = await changeHistoryService.recordChange(changeData);
res.status(201).json({
success: true,
data: change
});
} catch (error) {
console.error('Error recording change:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: '変更履歴の記録に失敗しました'
});
}
});
/**
* 一括変更履歴の記録(内部API)
* POST /api/history/bulk-record
*/
router.post('/bulk-record', async (req: Request, res: Response) => {
try {
const { changes } = req.body;
if (!Array.isArray(changes) || changes.length === 0) {
return res.status(400).json({
success: false,
error: 'Invalid changes array',
message: '有効な変更データの配列が必要です'
});
}
// 各変更データの検証
const requiredFields = ['recordId', 'domain', 'recordType', 'changeType', 'source'];
for (let i = 0; i < changes.length; i++) {
const change = changes[i];
const missingFields = requiredFields.filter(field => !change[field]);
if (missingFields.length > 0) {
return res.status(400).json({
success: false,
error: 'Invalid change data',
message: `変更データ ${i + 1} で必須フィールドが不足: ${missingFields.join(', ')}`
});
}
}
const recordedChanges = await changeHistoryService.bulkRecordChanges(changes);
res.status(201).json({
success: true,
data: {
recordedCount: recordedChanges.length,
changes: recordedChanges
}
});
} catch (error) {
console.error('Error bulk recording changes:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: '一括変更履歴の記録に失敗しました'
});
}
});
export { router as historyRouter };