wing-ops/backend/src/aerial/aerialRouter.ts
htlee ff085252b0 feat(phase4): Board/HNS/Prediction/Aerial/Rescue Mock → API 전환
- Board: 매뉴얼 CRUD + 첨부파일 API (012_board_ext.sql)
- HNS: 분석 CRUD 5개 API (013_hns_analysis.sql)
- Prediction: 분석/역추적/오일펜스 7개 API (014_prediction.sql)
- Aerial: 미디어/CCTV/위성 6개 API + PostGIS (015_aerial.sql)
- Rescue: 구난 작전/시나리오 3개 API + JSONB (016_rescue.sql)
- backtrackMockData.ts 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 01:17:10 +09:00

145 lines
5.0 KiB
TypeScript

import express from 'express';
import {
listMedia,
createMedia,
listCctv,
listSatRequests,
createSatRequest,
updateSatRequestStatus,
isValidSatStatus,
} from './aerialService.js';
import { isValidNumber } from '../middleware/security.js';
import { requireAuth, requirePermission } from '../auth/authMiddleware.js';
const router = express.Router();
// ============================================================
// AERIAL_MEDIA 라우트
// ============================================================
// GET /api/aerial/media — 미디어 목록
router.get('/media', requireAuth, requirePermission('aerial', 'READ'), async (req, res) => {
try {
const { equipType, mediaType, acdntSn, search } = req.query;
const acdntSnNum = acdntSn ? parseInt(acdntSn as string, 10) : undefined;
if (acdntSn && !isValidNumber(acdntSnNum, 1, 999999)) {
res.status(400).json({ error: '유효하지 않은 사고 번호' });
return;
}
const items = await listMedia({
equipType: equipType as string | undefined,
mediaType: mediaType as string | undefined,
acdntSn: acdntSnNum,
search: search as string | undefined,
});
res.json(items);
} catch (err) {
console.error('[aerial] 미디어 목록 오류:', err);
res.status(500).json({ error: '미디어 목록 조회 실패' });
}
});
// POST /api/aerial/media — 미디어 메타 등록
router.post('/media', requireAuth, requirePermission('aerial', 'CREATE'), async (req, res) => {
try {
const {
acdntSn, fileNm, orgnlNm, filePath, lon, lat, locDc,
equipTpCd, equipNm, mediaTpCd, takngDtm, fileSz, resolution,
} = req.body;
if (!fileNm) {
res.status(400).json({ error: '파일명은 필수입니다.' });
return;
}
const result = await createMedia({
acdntSn, fileNm, orgnlNm, filePath, lon, lat, locDc,
equipTpCd, equipNm, mediaTpCd, takngDtm, fileSz, resolution,
});
res.status(201).json(result);
} catch (err) {
console.error('[aerial] 미디어 등록 오류:', err);
res.status(500).json({ error: '미디어 등록 실패' });
}
});
// ============================================================
// CCTV_CAMERA 라우트
// ============================================================
// GET /api/aerial/cctv — CCTV 목록
router.get('/cctv', requireAuth, requirePermission('aerial', 'READ'), async (req, res) => {
try {
const { region, status } = req.query;
const items = await listCctv({
region: region as string | undefined,
status: status as string | undefined,
});
res.json(items);
} catch (err) {
console.error('[aerial] CCTV 목록 오류:', err);
res.status(500).json({ error: 'CCTV 목록 조회 실패' });
}
});
// ============================================================
// SAT_REQUEST 라우트
// ============================================================
// GET /api/aerial/satellite — 위성 촬영 요청 목록
router.get('/satellite', requireAuth, requirePermission('aerial', 'READ'), async (req, res) => {
try {
const { status } = req.query;
const items = await listSatRequests({
status: status as string | undefined,
});
res.json(items);
} catch (err) {
console.error('[aerial] 위성 요청 목록 오류:', err);
res.status(500).json({ error: '위성 요청 목록 조회 실패' });
}
});
// POST /api/aerial/satellite — 위성 촬영 요청 생성
router.post('/satellite', requireAuth, requirePermission('aerial', 'CREATE'), async (req, res) => {
try {
const {
reqCd, acdntSn, lon, lat, zoneDc, coordDc, zoneAreaKm2,
satNm, providerNm, resolution, purposeDc, reqstrNm, reqDtm, expectedRcvDtm,
} = req.body;
if (!reqCd) {
res.status(400).json({ error: '요청코드는 필수입니다.' });
return;
}
const result = await createSatRequest({
reqCd, acdntSn, lon, lat, zoneDc, coordDc, zoneAreaKm2,
satNm, providerNm, resolution, purposeDc, reqstrNm, reqDtm, expectedRcvDtm,
});
res.status(201).json(result);
} catch (err) {
console.error('[aerial] 위성 요청 생성 오류:', err);
res.status(500).json({ error: '위성 요청 생성 실패' });
}
});
// POST /api/aerial/satellite/:sn/status — 위성 요청 상태 변경
router.post('/satellite/:sn/status', requireAuth, requirePermission('aerial', 'CREATE'), async (req, res) => {
try {
const sn = parseInt(req.params.sn as string, 10);
if (!isValidNumber(sn, 1, 999999)) {
res.status(400).json({ error: '유효하지 않은 요청 번호' });
return;
}
const { sttsCd } = req.body;
if (!sttsCd || !isValidSatStatus(sttsCd)) {
res.status(400).json({ error: '유효하지 않은 상태값 (PENDING/SHOOTING/COMPLETED/CANCELLED)' });
return;
}
await updateSatRequestStatus(sn, sttsCd);
res.json({ success: true });
} catch (err) {
console.error('[aerial] 위성 요청 상태 변경 오류:', err);
res.status(500).json({ error: '위성 요청 상태 변경 실패' });
}
});
export default router;