GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
132 lines
4.6 KiB
JavaScript
132 lines
4.6 KiB
JavaScript
/**
|
||
* 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();
|