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>
231 lines
10 KiB
JavaScript
231 lines
10 KiB
JavaScript
/**
|
||
* 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 { REPO_ROOT, loadServerConfig } = require("../../resolve-project");
|
||
|
||
const PROJECT_NAME = "新・着工要因システム";
|
||
const ENV = "production";
|
||
const SOURCE_SITE_ID = 496626;
|
||
const MIRROR_FROM_USER_ID = 579;
|
||
const GRANT_TO_USER_NAME = "福島 成佳";
|
||
|
||
const baseDir = path.join(REPO_ROOT, PROJECT_NAME);
|
||
const configsDir = path.join(baseDir, "configs", ENV);
|
||
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 = loadServerConfig(ENV);
|
||
|
||
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, ENV);
|
||
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(configsDir, `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);
|
||
}
|
||
})();
|