/** * get-master-data.js * ------------------------------------------------------------ * Pleasanterのユーザー/組織(部署)/グループ一覧を取得し、 * ./configs/{env}/master/ 配下に保存します(resolve-names.js が読み込む)。 * ClaudePleasanter直下に置く共通実装。全プロジェクトの薄いラッパーから require される。 * 保存先はlayoutによらず共通(configs/{env}/master/)。 * * 使い方: * node get-master-data.js --project="..." --env=production * * 参照した公式マニュアル: * - 開発者向け機能:API:ユーザ操作:ユーザ取得(全て) * https://pleasanter.org/ja/manual/api-user-get-all * - 開発者向け機能:API:組織操作:組織取得 * https://pleasanter.org/ja/manual/api-dept-get * - 開発者向け機能:API:グループ操作:グループ取得 * https://pleasanter.org/ja/manual/api-group-get * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const { resolveProjectRoot, loadServerConfig } = require("./resolve-project"); const TARGETS = [ { key: "users", endpoint: "users/get", outFile: "master_users.json", label: "ユーザー", idField: "UserId" }, { key: "depts", endpoint: "depts/get", outFile: "master_depts.json", label: "組織", idField: "DeptId" }, { key: "groups", endpoint: "groups/get", outFile: "master_groups.json", label: "グループ", idField: "GroupId" }, ]; // 一部API(/api/users/get 等)はページング応答(TotalCount > 1回あたりのData件数)を返すため、 // 全件取得できるまで Offset をずらして繰り返し取得し、Data配列を結合する。 async function fetchAndSave(target, { BaseUrl, ApiKey, ApiVersion }, outDir) { const url = `${BaseUrl.replace(/\/+$/, "")}/api/${target.endpoint}`; let allData = []; let offset = 0; let totalCount = null; let firstJson = null; while (true) { const body = { ApiVersion: ApiVersion || "1.1", ApiKey: ApiKey, Offset: offset, }; console.log(`[INFO] 取得先: ${url} (Offset: ${offset})`); let response; try { response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); } catch (err) { console.error(`[エラー] ${target.label}の取得に失敗しました:`, err.message); return false; } const text = await response.text(); if (!response.ok) { console.error(`[エラー] HTTP ${response.status} ${response.statusText}`); console.error(text); return false; } let json; try { json = JSON.parse(text); } catch (err) { console.error(`[エラー] ${target.label}のレスポンスがJSONとして解析できませんでした。`); console.error(text); return false; } if (!firstJson) firstJson = json; const pageData = json?.Response?.Data || []; allData = allData.concat(pageData); totalCount = json?.Response?.TotalCount ?? allData.length; if (pageData.length === 0 || allData.length >= totalCount) break; offset = allData.length; } // ページ間でOffset起点がずれて重複が混ざる場合があるため、idFieldで重複排除する const seen = new Set(); allData = allData.filter((item) => { const id = item[target.idField]; if (seen.has(id)) return false; seen.add(id); return true; }); // 結合済みのData配列で1件目のレスポンスを上書きし、全件入りのJSONとして保存する const outJson = { ...firstJson, Response: { ...firstJson.Response, Data: allData } }; const outFile = path.join(outDir, target.outFile); fs.writeFileSync(outFile, JSON.stringify(outJson, null, 2), "utf-8"); console.log(`[OK] ${target.label}: ${allData.length}/${totalCount}件 保存しました: ${outFile}`); return true; } async function main() { const { baseDir, env } = resolveProjectRoot(); const { BaseUrl, ApiKey, ApiVersion } = loadServerConfig(env); const outDir = path.join(baseDir, "configs", env, "master"); if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); const results = []; for (const target of TARGETS) { const ok = await fetchAndSave(target, { BaseUrl, ApiKey, ApiVersion }, outDir); results.push({ target: target.label, ok }); } console.log("\n==== 取得結果まとめ ===="); for (const { target, ok } of results) { console.log(` ${target}: ${ok ? "OK" : "失敗"}`); } if (results.some((r) => !r.ok)) { process.exit(1); } } main();