/** * apply-permission-grant.js * ------------------------------------------------------------ * SiteId 496626 の権限継承先サイト(InheritPermission)に対し、 * UserId 579 と同じ権限をユーザー名「福島 成佳」に追加する。 * 480111側の継承元(426945)には適用しない。 * * Permissionsは SiteSettings 配下ではなくサイトデータ直下の項目であり、 * updatesitesettings(部分更新)API はPermissionsを更新対象に含まない * (公式マニュアルで確認済み)。そのため本スクリプトは updatesite(全体更新)を使い、 * 継承先サイトの現状の Title/ReferenceType/ParentId/InheritPermission/SiteSettings は * 一切変更せずそのまま再送信し、Permissionsのみ新規エントリを追加した配列を送信する。 * * ★注意: 公式マニュアルには updatesite のリクエストボディでPermissionsを * 指定できるという明記が無い(記載が省略されている可能性はある)。 * 実行前に必ずプレビュー出力を確認すること。 * * 差分プレビュー・送信結果は、権限継承先サイトの modify/{リクエストラベル}/ フォルダへ保存する * (「修正依頼のたびに新しいフォルダを生成する」規約。省略時はタイムスタンプを自動採番)。 * * 使い方: * node apply-permission-grant.js … 差分表示・アーティファクト生成のみ(送信なし) * node apply-permission-grant.js --execute … 上記に加えて実際に送信する * node apply-permission-grant.js --request=ラベル … modifyフォルダ名を明示指定 * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const { loadMasterData, resolveName, resolveId } = require("./resolve-names"); const { findSiteDir, latestJsonPath, newModifyDir, latestModifyDir } = require("./site-paths"); const SOURCE_SITE_ID = 496626; const MIRROR_FROM_USER_ID = 579; const GRANT_TO_USER_NAME = "福島 成佳"; const baseDir = path.join(__dirname, "..", ".."); const configsDir = path.join(baseDir, "configs"); const execute = process.argv.includes("--execute"); const requestArg = process.argv.find((a) => a.startsWith("--request=")); const requestLabel = requestArg ? requestArg.split("=")[1] : null; function loadJson(p, required = true) { if (!fs.existsSync(p)) { if (required) { console.error(`[エラー] ファイルが見つかりません: ${p}`); process.exit(1); } return null; } return JSON.parse(fs.readFileSync(p, "utf-8")); } function maskApiKey(key) { if (!key) return key; return key.length > 8 ? key.slice(0, 4) + "****" + key.slice(-4) : "****"; } function timestamp() { return new Date().toISOString().replace(/[:.]/g, "-"); } const config = loadJson(path.join(baseDir, "config.json")); const sourceSitePath = latestJsonPath(configsDir, SOURCE_SITE_ID) || path.join(configsDir, `site-${SOURCE_SITE_ID}_latest.json`); const sourceSiteJson = loadJson(sourceSitePath); const inheritPermissionId = sourceSiteJson?.Response?.Data?.InheritPermission; if (!inheritPermissionId) { console.error(`[エラー] ${sourceSitePath} からInheritPermissionを取得できませんでした。`); process.exit(1); } const targetPath = latestJsonPath(configsDir, inheritPermissionId) || path.join(configsDir, `site-${inheritPermissionId}_latest.json`); const targetJson = loadJson(targetPath, false); if (!targetJson) { console.error(`[エラー] ${targetPath} が見つかりません。先に node get-site-config.js を実行してください`); console.error(`(SiteId ${SOURCE_SITE_ID} 取得時にInheritPermission追跡ロジックで自動取得されるはずです)。`); process.exit(1); } const targetData = targetJson.Response.Data; const currentPermissions = targetData.Permissions || []; const targetSiteDir = findSiteDir(configsDir, inheritPermissionId); if (!targetSiteDir) { console.error(`[エラー] SiteId ${inheritPermissionId} のフォルダが見つかりません。`); process.exit(1); } // --request指定があればそれを使う。無ければ直近のmodifyフォルダを再利用し、 // まだ無ければ新規作成する(プレビュー→実行の一連の流れを同じフォルダにまとめるため)。 const modifyDir = requestLabel ? newModifyDir(targetSiteDir, requestLabel) : latestModifyDir(targetSiteDir) || newModifyDir(targetSiteDir); console.log(`[INFO] modifyフォルダ: ${modifyDir}`); console.log("========================================"); console.log(` 権限追加${execute ? "(実行)" : "(プレビューのみ)"}`); console.log("========================================"); console.log(`対象サイト: SiteId ${inheritPermissionId}("${targetData.Title}"、SiteId ${SOURCE_SITE_ID} の権限継承先)`); console.log(""); // UserId 579 と同じ権限エントリを抽出(複数PermissionTypeを持つ場合は全て対象) const sourceEntries = currentPermissions.filter((entry) => { const [type, id] = String(entry).split(","); return type === "User" && Number(id) === MIRROR_FROM_USER_ID; }); if (sourceEntries.length === 0) { console.error(`[エラー] UserId ${MIRROR_FROM_USER_ID} のPermissionsエントリが見つかりません。`); console.error(`現在のPermissions: ${JSON.stringify(currentPermissions)}`); process.exit(1); } // 福島 成佳 のUserIdを解決 const master = loadMasterData(baseDir); const candidateIds = resolveId(master, { Type: "User", Name: GRANT_TO_USER_NAME }); if (candidateIds.length === 0) { console.error(`[エラー] ユーザー名「${GRANT_TO_USER_NAME}」が見つかりません。configs/master_users.jsonを確認してください。`); process.exit(1); } if (candidateIds.length > 1) { console.error(`[エラー] ユーザー名「${GRANT_TO_USER_NAME}」に一致する候補が複数あります: ${candidateIds.join(", ")}`); process.exit(1); } const grantToUserId = candidateIds[0]; // 579のエントリをコピーし、ユーザーIDだけ差し替えた新規エントリを作成 const newEntries = sourceEntries.map((entry) => { const parts = String(entry).split(","); parts[1] = String(grantToUserId); return parts.join(","); }); // 既に同一エントリが存在する場合は追加しない const entriesToAdd = newEntries.filter((e) => !currentPermissions.includes(e)); console.log(`コピー元: UserId ${MIRROR_FROM_USER_ID} (${resolveName(master, { Type: "User", Id: MIRROR_FROM_USER_ID })})`); console.log(` 既存エントリ: ${JSON.stringify(sourceEntries)}`); console.log(`追加先: UserId ${grantToUserId} (${GRANT_TO_USER_NAME})`); console.log(` 追加予定エントリ: ${JSON.stringify(newEntries)}`); if (entriesToAdd.length === 0) { console.log("\n[INFO] 追加予定のエントリは既に全て存在しています。変更の必要はありません。"); process.exit(0); } if (entriesToAdd.length !== newEntries.length) { console.log(`[INFO] 一部エントリは既に存在するため、実際に追加するのは ${entriesToAdd.length} 件です: ${JSON.stringify(entriesToAdd)}`); } const updatedPermissions = [...currentPermissions, ...entriesToAdd]; console.log("\n★注意: updatesite APIでPermissionsを更新できるという公式記載は確認できていません。"); console.log(" 実行前に必ず内容を確認し、実行後は管理画面での反映結果も確認してください。"); const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${inheritPermissionId}/updatesite`; const body = { ApiVersion: config.ApiVersion || "1.1", ApiKey: config.ApiKey, SiteId: inheritPermissionId, Title: targetData.Title, ReferenceType: targetData.ReferenceType, ParentId: targetData.ParentId, InheritPermission: targetData.InheritPermission, Permissions: updatedPermissions, SiteSettings: targetData.SiteSettings, // 変更しない(現状のまま再送信) }; const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) }; console.log("\n--- 送信されるBody(APIキーはマスク表示、SiteSettingsは件数のみ表示) ---"); console.log( JSON.stringify( { ...maskedBody, SiteSettings: `(変更なし。Columns ${(targetData.SiteSettings?.Columns || []).length}件 等)` }, null, 2 ) ); const ts = timestamp(); const diffOutPath = path.join(modifyDir, `site-${inheritPermissionId}_permission_diff_${ts}.txt`); const diffText = [ `SiteId ${inheritPermissionId} 権限追加プレビュー (${ts})`, `URL: ${url}`, `対象: SiteId ${SOURCE_SITE_ID} の権限継承先`, "", `コピー元 UserId ${MIRROR_FROM_USER_ID} (${resolveName(master, { Type: "User", Id: MIRROR_FROM_USER_ID })})`, ` 既存エントリ: ${JSON.stringify(sourceEntries)}`, `追加先 UserId ${grantToUserId} (${GRANT_TO_USER_NAME})`, ` 追加エントリ: ${JSON.stringify(entriesToAdd)}`, "", `変更前Permissions: ${JSON.stringify(currentPermissions)}`, `変更後Permissions: ${JSON.stringify(updatedPermissions)}`, ].join("\n"); fs.writeFileSync(diffOutPath, diffText, "utf-8"); console.log(`\n[OK] 差分プレビューを保存しました: ${diffOutPath}`); if (!execute) { console.log("\n[注意] --execute が指定されていないため、送信は行っていません。"); console.log("内容を確認の上、問題なければ次を実行してください: node apply-permission-grant.js --execute"); process.exit(0); } (async () => { console.log("\n実際に送信します..."); const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await res.text(); const resultOutPath = path.join(baseDir, "configs", `site-${inheritPermissionId}_permission_result_${ts}.json`); fs.writeFileSync(resultOutPath, text, "utf-8"); console.log(`HTTP ${res.status} ${res.statusText}`); console.log(`[OK] 応答を保存しました: ${resultOutPath}`); try { const json = JSON.parse(text); if (json.StatusCode === 200) { console.log("[OK] 適用に成功しました。管理画面で反映結果を確認してください。"); } else { console.error("[エラー] 適用に失敗した可能性があります。応答内容を確認してください。"); } } catch { console.log(text); } })();