Files
Pole-Mapper/server/index.js
T

134 lines
3.9 KiB
JavaScript

import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import multer from 'multer';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import * as db from './db.js';
import { fetchGus, fetchDongs } from './vworld.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const UPLOAD_DIR = path.join(__dirname, 'uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
const PORT = Number(process.env.PORT || 4000);
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const app = express();
app.use(cors());
app.use(express.json());
app.use('/uploads', express.static(UPLOAD_DIR));
const upload = multer({
storage: multer.diskStorage({
destination: (_req, _file, cb) => cb(null, UPLOAD_DIR),
filename: (req, file, cb) => {
const id = req.body?.id || `pole-${Date.now()}`;
const ext = path.extname(file.originalname) || '.jpg';
cb(null, `${id}${ext}`);
},
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
app.get('/api/health', (_req, res) => res.json({ status: 'ok' }));
app.get('/api/gus', async (_req, res) => {
try {
if (!(await db.isGusFresh(CACHE_TTL_MS))) {
await db.replaceGus(await fetchGus());
}
res.json(await db.getGus());
} catch (e) {
res.status(502).json({ error: e.message });
}
});
app.get('/api/dongs', async (req, res) => {
const { gu } = req.query;
if (!gu) return res.status(400).json({ error: 'gu 파라미터가 필요합니다.' });
try {
if (!(await db.isDongsFresh(gu, CACHE_TTL_MS))) {
const guRow = await db.getGuByName(gu);
if (!guRow) return res.status(404).json({ error: `'${gu}' 구를 찾을 수 없습니다.` });
await db.replaceDongs(gu, await fetchDongs(guRow.code));
}
res.json(await db.getDongs(gu));
} catch (e) {
res.status(502).json({ error: e.message });
}
});
app.get('/api/poles', async (req, res) => {
const { gu = '', dong = '', start = '', end = '' } = req.query;
try {
res.json(await db.getAllPoles({ gu, dong, start, end }));
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.post('/api/poles', upload.single('photo'), async (req, res) => {
const { id, lat, lng, gu = '', dong = '', timestamp, level = '' } = req.body ?? {};
if (lat === undefined || lng === undefined || !timestamp) {
return res.status(400).json({ error: 'lat, lng, timestamp는 필수입니다.' });
}
const pole = {
id: id || `pole-${Date.now()}`,
lat: Number(lat),
lng: Number(lng),
gu,
dong,
timestamp,
level,
photo_path: req.file ? req.file.filename : null,
};
try {
await db.insertPole(pole);
} catch (e) {
return res.status(500).json({ error: e.message });
}
res.status(201).json({
...pole,
photo_path: undefined,
photoUrl: pole.photo_path ? `/uploads/${pole.photo_path}` : null,
});
});
app.get('/api/all-dongs', async (_req, res) => {
try {
if (!(await db.isGusFresh(CACHE_TTL_MS))) {
await db.replaceGus(await fetchGus());
}
const gus = await db.getGus();
for (const gu of gus) {
if (!(await db.isDongsFresh(gu.name, CACHE_TTL_MS))) {
await db.replaceDongs(gu.name, await fetchDongs(gu.code));
}
}
const allDongs = await db.getAllDongs();
res.json(allDongs);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.delete('/api/poles/:id', async (req, res) => {
const existing = await db.getPole(req.params.id);
if (!existing) return res.status(404).json({ error: '전신주를 찾을 수 없습니다.' });
if (existing.photo_path) {
fs.rm(path.join(UPLOAD_DIR, path.basename(existing.photo_path)), () => {});
}
try {
await db.deletePole(existing.id);
} catch (e) {
return res.status(500).json({ error: e.message });
}
res.json({ removed: true });
});
app.listen(PORT, () => {
console.log(`Pole Mapper API server: http://localhost:${PORT}`);
});