diff --git a/cypher-executor/src/routes/portal.ts b/cypher-executor/src/routes/portal.ts index 93f935e..e485bb4 100644 --- a/cypher-executor/src/routes/portal.ts +++ b/cypher-executor/src/routes/portal.ts @@ -631,6 +631,21 @@ portalRouter.patch('/portal/admin/users/:id', (c) => patch.libraries = JSON.stringify(body.libraries); } if (Object.keys(patch).length === 0) return c.json({ error: '沒有可更新的欄位(status/role/libraries)' }, 400); + + // 鎖死保護(P4,總管派工明定):**不可停用/降級最後一個 active admin**—— + // 否則系統再無人能管帳號(bootstrap 只能跑一次,409),變成鎖死狀態。 + // 只在「目標現在是 active admin 且 patch 會使它不再是」時才多打一次 list(平時零成本)。 + const isActiveAdmin = (rec.values.role ?? '') === 'admin' && (rec.values.status ?? '') === 'active'; + const wouldLoseAdmin = patch.status === 'disabled' || patch.role === 'user'; + if (isActiveAdmin && wouldLoseAdmin) { + const all = await listRecordsByTemplate(c.env, USER_TEMPLATE); + const otherActiveAdmins = all.filter( + (u) => u.record_id !== recordId && (u.values.role ?? '') === 'admin' && (u.values.status ?? '') === 'active', + ); + if (otherActiveAdmins.length === 0) { + return c.json({ error: '不可停用或降級最後一個管理員——系統至少要保留一個 active admin' }, 409); + } + } patch.updated_at = new Date().toISOString(); const updated = await patchRecordValues(c.env, recordId, patch);