ken_nogi/ClaudePleasanter/★マスターシート/.claude/js/apply-permission-grant.js
Kenichiro NOGI ed33892f08 chore: 作業中の変更を整理しコミット(複数プロジェクト分)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 11:09:50 +09:00

228 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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--- 送信されるBodyAPIキーはマスク表示、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);
}
})();