ken_nogi/NodeSrv/apps/org-master-sync/scripts/_tmp-build-import-csvs.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

106 lines
4.4 KiB
JavaScript

"use strict";
const fs = require("node:fs");
const path = require("node:path");
function parseCsv(text) {
const rows = [];
let row = [], field = "", inQuotes = false;
for (let i = 0; i < text.length; i++) {
const c = text[i];
if (inQuotes) {
if (c === '"') { if (text[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 === "\r") { /* skip */ }
else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
else field += c;
}
}
if (field.length > 0 || row.length > 0) { row.push(field); rows.push(row); }
const headers = rows.shift() || [];
return rows.filter((r) => !(r.length === 1 && r[0] === "")).map((r) => {
const obj = {};
headers.forEach((h, idx) => { obj[h] = r[idx] !== undefined ? r[idx] : ""; });
return obj;
});
}
function readCsv(filePath) {
let text = fs.readFileSync(filePath, "utf8");
if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);
return parseCsv(text);
}
function csvEscape(s) {
return '"' + String(s == null ? "" : s).replace(/"/g, '""') + '"';
}
function toCsv(rows, headers) {
const lines = [headers.map(csvEscape).join(",")];
for (const r of rows) lines.push(headers.map((h) => csvEscape(r[h])).join(","));
return lines.join("\r\n");
}
const sampleDir = path.join(__dirname, "..", "sample");
const outDir = path.join(__dirname, "..", "data", "test-output");
// ---- 組織 ----
const deptRows = readCsv(path.join(sampleDir, "組織_2026_09_03 13_53_35.csv"));
const deptOut = deptRows.map((d) => ({
組織ID: d["組織ID"],
組織名: d["組織名"],
組織コード: d["組織コード"],
説明: d["説明"],
}));
const deptCsv = toCsv(deptOut, ["組織ID", "組織名", "組織コード", "説明"]);
fs.writeFileSync(path.join(outDir, "mock_import_depts.csv"), deptCsv, "utf8");
console.log("組織: ", deptOut.length, "件");
// ---- ユーザー ----
const userRows = readCsv(path.join(sampleDir, "ユーザ_2026_09_03 13_53_13.csv"));
const userHeaders = [
"ユーザID", "ログインID", "名前", "ユーザコード", "生年月日", "性別", "言語", "タイムゾーン",
"組織コード", "組織", "管理者", "テーマ", "説明", "最終ログイン日時", "パスワード有効期限",
"パスワード変更日時", "ログイン回数", "ログイン失敗回数", "テナント管理者",
"サイトトップへの作成を許可", "グループの管理を許可", "グループの作成を許可", "APIを許可",
"サイトトップからの移動を許可", "2段階認証の有効化", "2段階認証の無効化", "無効", "ロック",
"ロックカウンター", "秘密鍵有効", "ログイン有効期限", "無効化までの日数", "メールアドレス", "パスワード",
];
const userOut = userRows.map((u) => {
const o = {};
for (const h of userHeaders) o[h] = u[h];
return o;
});
const userCsv = toCsv(userOut, userHeaders);
fs.writeFileSync(path.join(outDir, "mock_import_users.csv"), userCsv, "utf8");
console.log("ユーザー: ", userOut.length, "件");
// ---- グループ(1571行を59グループへ集約、メンバーはDescription列にテキスト格納) ----
const groupRows = readCsv(path.join(sampleDir, "グループ_2026_09_03 13_53_26.csv"));
const groupsMap = new Map();
for (const row of groupRows) {
const gid = row["グループID"];
if (!groupsMap.has(gid)) {
groupsMap.set(gid, {
グループID: gid,
グループ名: row["グループ名"],
説明: row["説明"],
members: [],
});
}
const g = groupsMap.get(gid);
if (row["メンバー種別"] && row["メンバーキー"]) {
g.members.push(`${row["メンバー種別"]},${row["メンバーキー"]},${row["メンバーは管理者"] === "1" ? "True" : "False"}`);
}
}
const groupOut = [...groupsMap.values()].map((g) => ({
グループID: g.グループID,
グループ名: g.グループ名,
説明: g.説明,
"グループメンバー(種別,キー,管理者フラグ 1行1メンバー)": g.members.join("\n"),
}));
const groupCsv = toCsv(groupOut, ["グループID", "グループ名", "説明", "グループメンバー(種別,キー,管理者フラグ 1行1メンバー)"]);
fs.writeFileSync(path.join(outDir, "mock_import_groups.csv"), groupCsv, "utf8");
console.log("グループ: ", groupOut.length, "件 (元1571行→集約)");