- Incidents 통합 분석 시 이전 분석 결과를 분할 화면으로 표출 - 유출유/HNS/구난 분석 선택 모달(AnalysisSelectModal) 추가 - prediction /analyses/:acdntSn/oil-summary API 신규 (primary + byModel) - HNS 분석 생성 시 acdntSn 연결 지원 - GSC 사고 목록 응답에 acdntSn 노출 - 민감자원 누적/카테고리 관리 및 HNS 확산 레이어 유틸(hnsDispersionLayers) 추가
158 lines
5.9 KiB
TypeScript
158 lines
5.9 KiB
TypeScript
import express from 'express'
|
|
import { searchSubstances, getSubstanceById, listAnalyses, getAnalysis, createAnalysis, updateAnalysisResult, deleteAnalysis } from './hnsService.js'
|
|
import { isValidNumber } from '../middleware/security.js'
|
|
import { requireAuth, requirePermission } from '../auth/authMiddleware.js'
|
|
|
|
const router = express.Router()
|
|
|
|
// ============================================================
|
|
// HNS 분석 라우트 (/:id 보다 먼저 등록)
|
|
// ============================================================
|
|
|
|
// GET /api/hns/analyses — 분석 목록
|
|
router.get('/analyses', requireAuth, requirePermission('hns', 'READ'), async (req, res) => {
|
|
try {
|
|
const { status, substance, search, acdntSn } = req.query
|
|
const acdntSnNum = acdntSn ? parseInt(acdntSn as string, 10) : undefined
|
|
const items = await listAnalyses({
|
|
status: status as string | undefined,
|
|
substance: substance as string | undefined,
|
|
search: search as string | undefined,
|
|
acdntSn: acdntSnNum && !Number.isNaN(acdntSnNum) ? acdntSnNum : undefined,
|
|
})
|
|
res.json(items)
|
|
} catch (err) {
|
|
console.error('[hns] 분석 목록 오류:', err)
|
|
res.status(500).json({ error: 'HNS 분석 목록 조회 실패' })
|
|
}
|
|
})
|
|
|
|
// GET /api/hns/analyses/:sn — 분석 상세
|
|
router.get('/analyses/:sn', requireAuth, requirePermission('hns', 'READ'), 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 item = await getAnalysis(sn)
|
|
if (!item) {
|
|
res.status(404).json({ error: '분석을 찾을 수 없습니다' })
|
|
return
|
|
}
|
|
res.json(item)
|
|
} catch (err) {
|
|
console.error('[hns] 분석 상세 오류:', err)
|
|
res.status(500).json({ error: 'HNS 분석 조회 실패' })
|
|
}
|
|
})
|
|
|
|
// POST /api/hns/analyses — 분석 생성
|
|
router.post('/analyses', requireAuth, requirePermission('hns', 'CREATE'), async (req, res) => {
|
|
try {
|
|
const { anlysNm, acdntSn, acdntDtm, locNm, lon, lat, sbstNm, spilQty, spilUnitCd, fcstHr, algoCd, critMdlCd, windSpd, windDir, temp, humid, atmStblCd, analystNm } = req.body
|
|
if (!anlysNm) {
|
|
res.status(400).json({ error: '분석명은 필수입니다.' })
|
|
return
|
|
}
|
|
const acdntSnNum = acdntSn != null ? parseInt(String(acdntSn), 10) : undefined
|
|
const result = await createAnalysis({
|
|
anlysNm, acdntSn: acdntSnNum && !Number.isNaN(acdntSnNum) ? acdntSnNum : undefined,
|
|
acdntDtm, locNm, lon, lat, sbstNm, spilQty, spilUnitCd, fcstHr, algoCd, critMdlCd, windSpd, windDir, temp, humid, atmStblCd, analystNm,
|
|
})
|
|
res.status(201).json(result)
|
|
} catch (err) {
|
|
console.error('[hns] 분석 생성 오류:', err)
|
|
res.status(500).json({ error: 'HNS 분석 생성 실패' })
|
|
}
|
|
})
|
|
|
|
// POST /api/hns/analyses/:sn/save — 분석 결과 저장 (PUT 금지 정책 준수)
|
|
router.post('/analyses/:sn/save', requireAuth, requirePermission('hns', '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 { rsltData, execSttsCd, riskCd } = req.body
|
|
if (!rsltData) {
|
|
res.status(400).json({ error: '결과 데이터는 필수입니다.' })
|
|
return
|
|
}
|
|
await updateAnalysisResult(sn, { rsltData, execSttsCd, riskCd })
|
|
res.json({ success: true })
|
|
} catch (err) {
|
|
console.error('[hns] 분석 결과 저장 오류:', err)
|
|
res.status(500).json({ error: 'HNS 분석 결과 저장 실패' })
|
|
}
|
|
})
|
|
|
|
// DELETE /api/hns/analyses/:sn — 분석 삭제
|
|
router.delete('/analyses/:sn', requireAuth, requirePermission('hns', 'DELETE'), async (req, res) => {
|
|
try {
|
|
const sn = parseInt(req.params.sn as string, 10)
|
|
if (!isValidNumber(sn, 1, 999999)) {
|
|
res.status(400).json({ error: '유효하지 않은 분석 번호' })
|
|
return
|
|
}
|
|
await deleteAnalysis(sn)
|
|
res.json({ success: true })
|
|
} catch (err) {
|
|
console.error('[hns] 분석 삭제 오류:', err)
|
|
res.status(500).json({ error: 'HNS 분석 삭제 실패' })
|
|
}
|
|
})
|
|
|
|
// ============================================================
|
|
// HNS 물질 라우트
|
|
// ============================================================
|
|
|
|
// HNS 물질 검색
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const q = req.query.q as string | undefined
|
|
const type = req.query.type as string | undefined
|
|
const sebc = req.query.sebc as string | undefined
|
|
const page = parseInt(req.query.page as string, 10) || 1
|
|
const limit = parseInt(req.query.limit as string, 10) || 50
|
|
|
|
if (!isValidNumber(page, 1, 10000) || !isValidNumber(limit, 1, 100)) {
|
|
return res.status(400).json({
|
|
error: '유효하지 않은 페이지네이션',
|
|
message: 'page는 1~10000, limit은 1~100 범위여야 합니다.',
|
|
})
|
|
}
|
|
|
|
const validTypes = ['abbreviation', 'nameKr', 'nameEn', 'casNumber', 'unNumber', 'cargoCode']
|
|
const searchType = type && validTypes.includes(type)
|
|
? type as 'abbreviation' | 'nameKr' | 'nameEn' | 'casNumber' | 'unNumber' | 'cargoCode'
|
|
: undefined
|
|
|
|
const result = await searchSubstances({ q, type: searchType, sebc, page, limit })
|
|
res.json(result)
|
|
} catch {
|
|
res.status(500).json({ error: 'HNS 물질 검색 실패' })
|
|
}
|
|
})
|
|
|
|
// HNS 물질 상세 조회
|
|
router.get('/:id', async (req, res) => {
|
|
try {
|
|
const id = parseInt(req.params.id, 10)
|
|
if (!isValidNumber(id, 1, 999999)) {
|
|
return res.status(400).json({ error: '유효하지 않은 물질 ID' })
|
|
}
|
|
|
|
const substance = await getSubstanceById(id)
|
|
if (!substance) {
|
|
return res.status(404).json({ error: '물질을 찾을 수 없습니다' })
|
|
}
|
|
res.json(substance)
|
|
} catch {
|
|
res.status(500).json({ error: 'HNS 물질 조회 실패' })
|
|
}
|
|
})
|
|
|
|
export default router
|