feat(permissions): B8 — временные права (expires_at) с авто-снятием
Миграция 053: user_permissions.expires_at (NULL = бессрочно). Резолвер isEnabled
+ /me + /users/:id игнорируют просроченные оверрайды (наследуют роль); seedDefaults
чистит просроченные строки. setUserPermission принимает days → выдаёт право на
срок (datetime('now','+N days')). API отдаёт expiresAt. Клиент: setUserPermission(...,days).
В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.» (выдать на N дней).
Тест: срок хранится/отдаётся, просроченное игнорируется и вычищается. Backend pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -364,6 +364,12 @@
|
||||
const badge = hasOverride
|
||||
? `<span style="font-size:11px;padding:2px 5px;border-radius:var(--r-pill);background:rgba(155,93,229,0.12);color:var(--violet);font-weight:700">Индивидуально</span>`
|
||||
: `<span style="font-size:10px;padding:2px 7px;border-radius:var(--r-pill);background:rgba(136,152,170,0.12);color:var(--text-3);font-weight:700">По роли</span>`;
|
||||
const expBadge = (hasOverride && p.expiresAt)
|
||||
? `<span title="Временный оверрайд истекает (UTC)" style="font-size:11px;padding:2px 6px;border-radius:var(--r-pill);background:rgba(245,158,11,0.14);color:#b45309;font-weight:700">до ${esc(p.expiresAt.slice(0, 10))}</span>`
|
||||
: '';
|
||||
const tempBtn = `<button style="background:none;border:none;cursor:pointer;color:var(--text-3);padding:3px 6px;border-radius:6px;font-size:11px;font-weight:600"
|
||||
onmouseover="this.style.color='var(--violet)'" onmouseout="this.style.color='var(--text-3)'"
|
||||
onclick="doSetUserPermTemp('${esc(p.key)}')" title="Выдать право на срок">врем.</button>`;
|
||||
const resetBtn = hasOverride
|
||||
? `<button style="background:none;border:none;cursor:pointer;color:var(--text-3);padding:3px 6px;border-radius:6px;font-size:11px;font-weight:700;transition:color .2s"
|
||||
onmouseover="this.style.color='var(--danger)'" onmouseout="this.style.color='var(--text-3)'"
|
||||
@@ -372,9 +378,11 @@
|
||||
return `
|
||||
<div class="perm-card${checked ? ' enabled' : ''}" id="up-perm-card-${p.key.replace('.','_')}">
|
||||
<div class="perm-info">
|
||||
<div style="display:flex;align-items:center;gap:7px">
|
||||
<div style="display:flex;align-items:center;gap:7px;flex-wrap:wrap">
|
||||
<span class="perm-label">${esc(p.label)}</span>
|
||||
${badge}
|
||||
${expBadge}
|
||||
${tempBtn}
|
||||
${resetBtn}
|
||||
</div>
|
||||
<div class="perm-desc">${esc(p.desc)}</div>
|
||||
@@ -408,6 +416,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function doSetUserPermTemp(key) {
|
||||
const uid = getActiveUid();
|
||||
if (!uid) return;
|
||||
const raw = window.prompt('Выдать право временно. На сколько дней?', '7');
|
||||
if (raw === null) return;
|
||||
const days = parseInt(raw, 10);
|
||||
if (!Number.isInteger(days) || days <= 0) { LS.toast('Введите число дней > 0', 'error'); return; }
|
||||
try {
|
||||
await LS.setUserPermission(uid, key, true, days);
|
||||
_upPermsData = await LS.getUserPermissions(uid);
|
||||
renderUserPerms();
|
||||
LS.toast(`Право выдано на ${days} дн.`, 'success');
|
||||
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function doResetOneUserPerm(key) {
|
||||
const uid = getActiveUid();
|
||||
if (!uid) return;
|
||||
@@ -445,6 +468,7 @@
|
||||
window.closeUserPermsModal = closeUserPermsModal;
|
||||
window.openUserPermsModal = openUserPermsModal;
|
||||
window.doSetUserPerm = doSetUserPerm;
|
||||
window.doSetUserPermTemp = doSetUserPermTemp;
|
||||
window.doResetOneUserPerm = doResetOneUserPerm;
|
||||
window.doResetAllUserPerms = doResetAllUserPerms;
|
||||
// Phase 5 quick actions
|
||||
|
||||
Reference in New Issue
Block a user