ken_nogi/NodeSrv/apps/org-master-sync/scripts/import-prod-to-test.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
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>
2026-09-04 15:37:06 +09:00

170 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
/*
* 本番プリザンター(data/*.csv)からエクスポート済みのDepts/Users/Groupsを
* テスト環境(PLEASANTER_TEST_BASE_URL)へ投入する一回限りの移行スクリプト。
*
* 実行:
* PLEASANTER_TEST_BASE_URL=... PLEASANTER_TEST_API_KEY=... TEST_USER_PASSWORD=... node scripts/import-prod-to-test.js
*/
const fs = require("fs");
const path = require("path");
const BASE_URL = process.env.PLEASANTER_TEST_BASE_URL;
const API_KEY = process.env.PLEASANTER_TEST_API_KEY;
const PASSWORD = process.env.TEST_USER_PASSWORD;
const DATA_DIR = path.join(__dirname, "..", "data");
if (!BASE_URL || !API_KEY || !PASSWORD) {
console.error("PLEASANTER_TEST_BASE_URL / PLEASANTER_TEST_API_KEY / TEST_USER_PASSWORD が未設定");
process.exit(1);
}
function parseCsv(text) {
const rows = [];
let row = [], field = "", inQuotes = false;
const s = text.replace(/^/, "");
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (inQuotes) {
if (c === '"') { if (s[i + 1] === '"') { field += '"'; i++; } else inQuotes = false; }
else field += c;
} else {
if (c === '"') inQuotes = true;
else if (c === ",") { row.push(field); field = ""; }
else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
else if (c === "\r") { /* skip */ }
else field += c;
}
}
if (field.length || row.length) { row.push(field); rows.push(row); }
const header = rows[0];
return rows.slice(1).filter(r => r.length === header.length).map(r => Object.fromEntries(header.map((h, i) => [h, r[i]])));
}
async function api(pathname, body) {
const res = await fetch(BASE_URL.replace(/\/$/, "") + pathname, {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({ ApiVersion: "1.1", ApiKey: API_KEY, ...body }),
});
const data = await res.json();
if (!res.ok || data.StatusCode !== 200) {
throw new Error(`${pathname} failed: ${res.status} ${JSON.stringify(data)}`);
}
return data;
}
async function importDepts() {
const depts = parseCsv(fs.readFileSync(path.join(DATA_DIR, "pleasanter_depts.csv"), "utf8"));
const deptIdMap = new Map(); // 本番DeptId -> テスト環境DeptId
let ok = 0, ng = 0;
for (const d of depts) {
try {
const res = await api("/api/depts/create", { DeptCode: d.DeptCode, DeptName: d.DeptName });
deptIdMap.set(d.DeptId, res.Id);
ok++;
} catch (e) {
console.error("[dept]", d.DeptId, d.DeptName, e.message);
ng++;
}
}
console.log(`Depts: ok=${ok} ng=${ng}`);
return deptIdMap;
}
async function importUsers(deptIdMap) {
const users = parseCsv(fs.readFileSync(path.join(DATA_DIR, "pleasanter_users.csv"), "utf8"))
.filter(u => u.UserId !== "1");
const userIdMap = new Map(); // 本番UserId -> テスト環境UserId
let ok = 0, ng = 0;
for (const u of users) {
try {
const mail = (u.MailAddresses || "").split(";").filter(Boolean);
const newDeptId = deptIdMap.get(u.DeptId);
const res = await api("/api/users/create", {
LoginId: u.LoginId,
Name: u.Name,
Password: PASSWORD,
MailAddresses: mail,
DeptId: newDeptId || 0,
GlobalId: `pleasanter-prod-${u.UserId}`,
});
userIdMap.set(u.UserId, res.Id);
ok++;
} catch (e) {
console.error("[user]", u.UserId, u.LoginId, e.message);
ng++;
}
}
console.log(`Users: ok=${ok} ng=${ng}`);
return userIdMap;
}
async function importGroups(userIdMap, deptIdMap) {
const groups = parseCsv(fs.readFileSync(path.join(DATA_DIR, "pleasanter_groups.csv"), "utf8"));
const groupIdMap = new Map(); // 本番GroupId -> テスト環境GroupId
let ok = 0, ng = 0;
// 1st pass: 空メンバーで作成しID採番
for (const g of groups) {
try {
const res = await api("/api/groups/create", { GroupName: g.GroupName });
groupIdMap.set(g.GroupId, res.Id);
ok++;
} catch (e) {
console.error("[group create]", g.GroupId, g.GroupName, e.message);
ng++;
}
}
console.log(`Groups(create): ok=${ok} ng=${ng}`);
// 2nd pass: GroupMembers/GroupChildrenを本番ID→テスト環境IDへ変換してUpdate
let uOk = 0, uNg = 0;
for (const g of groups) {
const newGroupId = groupIdMap.get(g.GroupId);
if (!newGroupId) continue;
const members = (g.GroupMembers || "").split(";").filter(Boolean).map(m => {
const [kind, id, flag] = m.split(",");
if (kind === "User") {
const nid = userIdMap.get(id);
return nid ? `User,${nid},${flag}` : null;
}
if (kind === "Dept") {
const nid = deptIdMap.get(id);
return nid ? `Dept,${nid},${flag}` : null;
}
return null;
}).filter(Boolean);
const children = (g.GroupChildren || "").split(";").filter(Boolean).map(c => {
const [, id] = c.split(",");
const nid = groupIdMap.get(id);
return nid ? `Group,${nid},` : null;
}).filter(Boolean);
try {
await api(`/api/groups/${newGroupId}/update`, { GroupMembers: members, GroupChildren: children });
uOk++;
} catch (e) {
console.error("[group update]", g.GroupId, "->", newGroupId, e.message);
uNg++;
}
}
console.log(`Groups(update members): ok=${uOk} ng=${uNg}`);
return groupIdMap;
}
async function main() {
const deptIdMap = await importDepts();
const userIdMap = await importUsers(deptIdMap);
const groupIdMap = await importGroups(userIdMap, deptIdMap);
const mapDir = path.join(DATA_DIR, "test-import-maps");
fs.mkdirSync(mapDir, { recursive: true });
fs.writeFileSync(path.join(mapDir, "dept-id-map.json"), JSON.stringify(Object.fromEntries(deptIdMap), null, 2));
fs.writeFileSync(path.join(mapDir, "user-id-map.json"), JSON.stringify(Object.fromEntries(userIdMap), null, 2));
fs.writeFileSync(path.join(mapDir, "group-id-map.json"), JSON.stringify(Object.fromEntries(groupIdMap), null, 2));
console.log("完了。IDマッピングを data/test-import-maps/ に保存");
}
main().catch(e => { console.error(e); process.exit(1); });