aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/park-editor.mjs
blob: a872008e3c24348253f67bd00a112bf602986b3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// 公園・公共施設データの簡易エディタ(ローカル専用)。
//
// 使い方:
//   npm run park-editor
//   → 表示された URL(既定 http://localhost:8787/)をブラウザで開く
//   → タブで「市内公園 / 周辺の公園 / 市内の公共施設 / 周辺の公共施設」を切り替え
//   → 表のセルを直接編集し、「保存」を押すと該当するJSONを更新
//   → 反映確認後、git にコミットする
//
// 注意: ローカル(127.0.0.1)にのみ公開します。追加パッケージは不要です。
import { createServer } from "node:http";
import { readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const HTML = join(root, "scripts/park-editor.html");
const PORT = Number(process.env.PORT || 8787);

const DATASETS = {
  parks: {
    label: "市内公園",
    path: join(root, "src/data/kodaira-parks.json"),
    pretty: false,
  },
  nearbyParks: {
    label: "周辺の公園",
    path: join(root, "src/data/kodaira-nearby-parks.json"),
    pretty: false,
  },
  facilities: {
    label: "市内の公共施設",
    path: join(root, "src/data/kodaira-facilities.json"),
    pretty: true,
  },
  nearbyFacilities: {
    label: "周辺の公共施設",
    path: join(root, "src/data/kodaira-nearby-facilities.json"),
    pretty: true,
  },
};

function send(res, code, type, body) {
  res.writeHead(code, { "content-type": type });
  res.end(body);
}

function datasetFromUrl(url) {
  const match = /^\/api\/data\/([A-Za-z]+)$/.exec(url);
  if (!match) return null;
  return DATASETS[match[1]] ? { id: match[1], dataset: DATASETS[match[1]] } : null;
}

const server = createServer(async (req, res) => {
  try {
    const url = (req.url || "").split("?")[0];
    if (req.method === "GET" && (url === "/" || url === "/index.html")) {
      send(res, 200, "text/html; charset=utf-8", await readFile(HTML, "utf-8"));
      return;
    }

    if (req.method === "GET" && url === "/api/datasets") {
      send(
        res,
        200,
        "application/json; charset=utf-8",
        JSON.stringify(
          Object.entries(DATASETS).map(([id, dataset]) => ({ id, label: dataset.label })),
        ),
      );
      return;
    }

    const target = datasetFromUrl(url);
    if (!target) {
      send(res, 404, "text/plain; charset=utf-8", "not found");
      return;
    }

    if (req.method === "GET") {
      send(
        res,
        200,
        "application/json; charset=utf-8",
        await readFile(target.dataset.path, "utf-8"),
      );
      return;
    }

    if (req.method === "POST") {
      let body = "";
      for await (const chunk of req) body += chunk;
      const data = JSON.parse(body);

      if (target.id === "nearbyParks") {
        if (!data || data.type !== "FeatureCollection" || !Array.isArray(data.features)) {
          throw new Error("周辺の公園は FeatureCollection ではありません");
        }
      } else if (!Array.isArray(data) || data.length === 0) {
        throw new Error("データが配列ではありません");
      }

      const text = target.dataset.pretty
        ? `${JSON.stringify(data, null, 2)}\n`
        : `${JSON.stringify(data)}\n`;
      await writeFile(target.dataset.path, text, "utf-8");

      const count = target.id === "nearbyParks" ? data.features.length : data.length;
      console.log(`保存しました: ${target.dataset.label} ${count}件 → ${target.dataset.path}`);
      send(
        res,
        200,
        "application/json; charset=utf-8",
        JSON.stringify({ ok: true, count }),
      );
      return;
    }

    send(res, 405, "text/plain; charset=utf-8", "method not allowed");
  } catch (e) {
    console.error(e);
    send(res, 500, "text/plain; charset=utf-8", String(e));
  }
});

server.listen(PORT, "127.0.0.1", () => {
  console.log(`公園・施設データ編集: http://localhost:${PORT}/  (Ctrl+C で終了)`);
});