"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); });