// org-master-sync代替マスタ: 組織(504736)/ユーザー(504737)/グループ(504738)へ // Users/Depts/Groups相当の項目(Columns)を新規追加するワンショットスクリプト。 // 対象は真っさらな空サイト(SiteSettingsにColumns未設定)。updatesite(Mode:full)で送信する。 // --execute指定時のみ実際に送信。指定なければプレビュー(送信Body・diff)のみ出力。 "use strict"; const fs = require("fs"); const path = require("path"); const PROJECT_NAME = "org-master-sync代替マスタ"; const ENV = "staging"; const REPO_ROOT = path.join(__dirname, "..", "..", "..", ".."); const PROJECT_ROOT = path.join(REPO_ROOT, PROJECT_NAME); function loadServerConfig() { const configPath = path.join(REPO_ROOT, `config_${ENV}.json`); const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); if (!config.BaseUrl || !config.ApiKey) { console.error(`[エラー] ${configPath} に BaseUrl / ApiKey を設定してください。`); process.exit(1); } return config; } function siteDir(siteId, siteName) { return path.join(PROJECT_ROOT, "configs", ENV, `site-${siteId}_${siteName}`); } function loadLatest(siteId, siteName) { const p = path.join(siteDir(siteId, siteName), "sitesettings", `site-${siteId}_latest.json`); const raw = JSON.parse(fs.readFileSync(p, "utf-8")); return raw.Response.Data; } const TARGETS = [ { // Depts(組織) API全フィールド対応。Title/Body/Commentsは使わず全部Class/Description列で表現 siteId: 504736, siteName: "組織", titleColumns: [], columns: [ { ColumnName: "ClassA", LabelText: "組織ID" }, { ColumnName: "ClassB", LabelText: "組織名", FieldCss: "field-wide" }, { ColumnName: "ClassC", LabelText: "組織コード" }, { ColumnName: "Description001", LabelText: "説明" }, ], gridColumns: ["ResultId", "ClassA", "ClassB", "ClassC", "Updator", "UpdatedTime"], editorGeneral: ["ResultId", "Ver", "ClassA", "ClassB", "ClassC", "Description001"], }, { // Users(ユーザー) sample CSV(ユーザ_2026_09_03 13_53_13.csv)の列順・内容に準拠(コメント列のみ除外)。 // Title/Body/Commentsは使わず全部Class/Num/Date/Description/Check列で表現 siteId: 504737, siteName: "ユーザー", titleColumns: [], columns: [ { ColumnName: "ClassA", LabelText: "ユーザID" }, { ColumnName: "ClassB", LabelText: "ログインID", FieldCss: "field-wide" }, { ColumnName: "ClassC", LabelText: "名前", FieldCss: "field-wide" }, { ColumnName: "ClassD", LabelText: "ユーザコード" }, { ColumnName: "DateA", LabelText: "生年月日" }, { ColumnName: "ClassE", LabelText: "性別" }, { ColumnName: "ClassF", LabelText: "言語" }, { ColumnName: "ClassG", LabelText: "タイムゾーン" }, { ColumnName: "ClassH", LabelText: "組織コード" }, { ColumnName: "ClassI", LabelText: "組織" }, { ColumnName: "ClassJ", LabelText: "管理者" }, { ColumnName: "ClassK", LabelText: "テーマ" }, { ColumnName: "Description001", LabelText: "説明" }, { ColumnName: "DateB", LabelText: "最終ログイン日時" }, { ColumnName: "DateC", LabelText: "パスワード有効期限" }, { ColumnName: "DateD", LabelText: "パスワード変更日時" }, { ColumnName: "NumA", LabelText: "ログイン回数" }, { ColumnName: "NumB", LabelText: "ログイン失敗回数" }, { ColumnName: "CheckA", LabelText: "テナント管理者" }, { ColumnName: "CheckB", LabelText: "サイトトップへの作成を許可" }, { ColumnName: "CheckC", LabelText: "グループの管理を許可" }, { ColumnName: "CheckD", LabelText: "グループの作成を許可" }, { ColumnName: "CheckE", LabelText: "APIを許可" }, { ColumnName: "CheckF", LabelText: "サイトトップからの移動を許可" }, { ColumnName: "CheckG", LabelText: "2段階認証の有効化" }, { ColumnName: "CheckH", LabelText: "2段階認証の無効化" }, { ColumnName: "CheckI", LabelText: "無効" }, { ColumnName: "CheckJ", LabelText: "ロック" }, { ColumnName: "NumC", LabelText: "ロックカウンター" }, { ColumnName: "CheckK", LabelText: "秘密鍵有効" }, { ColumnName: "DateE", LabelText: "ログイン有効期限" }, { ColumnName: "NumD", LabelText: "無効化までの日数" }, { ColumnName: "ClassL", LabelText: "メールアドレス", FieldCss: "field-wide" }, { ColumnName: "ClassM", LabelText: "パスワード" }, ], gridColumns: ["ResultId", "ClassA", "ClassB", "ClassC", "ClassH", "CheckI", "CheckJ", "Updator", "UpdatedTime"], editorGeneral: [ "ResultId", "Ver", "ClassA", "ClassB", "ClassC", "ClassD", "DateA", "ClassE", "ClassF", "ClassG", "ClassH", "ClassI", "ClassJ", "ClassK", "Description001", "DateB", "DateC", "DateD", "NumA", "NumB", "CheckA", "CheckB", "CheckC", "CheckD", "CheckE", "CheckF", "CheckG", "CheckH", "CheckI", "CheckJ", "NumC", "CheckK", "DateE", "NumD", "ClassL", "ClassM", ], }, { // Groups(グループ) API全フィールド対応。 // GroupMembersはUser以外にDept/Groupも混在する(実データで確認済み)ため、 // 選択肢型(Users限定)ではなくDescription列にAPI形式のテキスト("種別,キー,管理者フラグ"を改行区切り)で格納する siteId: 504738, siteName: "グループ", titleColumns: [], columns: [ { ColumnName: "ClassA", LabelText: "グループID" }, { ColumnName: "ClassB", LabelText: "グループ名", FieldCss: "field-wide" }, { ColumnName: "Description001", LabelText: "説明" }, { ColumnName: "Description002", LabelText: "グループメンバー(種別,キー,管理者フラグ 1行1メンバー)", FieldCss: "field-wide" }, ], gridColumns: ["ResultId", "ClassA", "ClassB", "Updator", "UpdatedTime"], editorGeneral: ["ResultId", "Ver", "ClassA", "ClassB", "Description001", "Description002"], }, ]; function buildDesiredSiteSettings(current, target) { const settings = JSON.parse(JSON.stringify(current.SiteSettings)); settings.TitleColumns = target.titleColumns; settings.Columns = target.columns; settings.GridColumns = target.gridColumns; settings.EditorColumnHash = { General: target.editorGeneral }; settings.Links = current.SiteSettings.Links || []; return settings; } function buildUpdateBody(apiKey, siteId, current, desiredSettings) { return { ApiVersion: "1.1", ApiKey: apiKey, SiteId: siteId, Title: current.Title, ReferenceType: current.ReferenceType, ParentId: current.ParentId, InheritPermission: current.InheritPermission, SiteSettings: desiredSettings, }; } const execute = process.argv.includes("--execute"); const server = loadServerConfig(); (async () => { for (const target of TARGETS) { const current = loadLatest(target.siteId, target.siteName); const desiredSettings = buildDesiredSiteSettings(current, target); const body = buildUpdateBody(server.ApiKey, target.siteId, current, desiredSettings); const outDir = siteDir(target.siteId, target.siteName); const previewPath = path.join(outDir, "add-columns_preview.json"); fs.writeFileSync(previewPath, JSON.stringify(body, null, 2), "utf-8"); console.log(`\n==== SiteId ${target.siteId} (${target.siteName}) ====`); console.log("追加する項目:"); for (const c of target.columns) console.log(` - ${c.ColumnName}: ${c.LabelText}`); console.log(`プレビュー保存先: ${previewPath}`); if (!execute) { console.log("(--execute 未指定のためプレビューのみ。送信していません)"); continue; } const url = `${server.BaseUrl}api/items/${target.siteId}/updatesite`; console.log(`送信先: ${url}`); const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const json = await res.json(); console.log(`[結果] HTTP ${res.status}:`, JSON.stringify(json)); const resultPath = path.join(outDir, `add-columns_result_${Date.now()}.json`); fs.writeFileSync(resultPath, JSON.stringify(json, null, 2), "utf-8"); } })();