CC2 R4 #4: /api/users/me/gdpr-export alias
- New auth.gdpr.me_router prefix /api/users/me with: - GET/POST /gdpr-export → Art.20 JSON download with Content-Disposition - POST /gdpr-erase → Art.17 erasure request - GET /gdpr-consent → consent history for caller - jsonable_encoder fixes datetime serialisation in JSONResponse - admin_users.html: 'Izvezi moje podatke' now POSTs to alias and uses filename from Content-Disposition header - 401 enforced on no-auth, 200 on valid Bearer (verified live)
This commit is contained in:
+39
-2
@@ -150,13 +150,26 @@ table tr:hover td { background: rgba(26, 115, 232, 0.05); }
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="clanarine" onclick="setTab('clanarine')">€ Članarine <span class="count" id="cnt-clanarine">…</span></div>
|
||||
<div class="tab active" data-tab="clanovi" onclick="setTab('clanovi')">👤 Članovi <span class="count" id="cnt-clanovi">…</span></div>
|
||||
<div class="tab" data-tab="clanarine" onclick="setTab('clanarine')">€ Članarine <span class="count" id="cnt-clanarine">…</span></div>
|
||||
<div class="tab" data-tab="lijecnicki" onclick="setTab('lijecnicki')">⚕ Liječnički pregledi <span class="count" id="cnt-lijecnicki">…</span></div>
|
||||
<div class="tab" data-tab="obrasci" onclick="setTab('obrasci')">📝 Obrasci <span class="count" id="cnt-obrasci">…</span></div>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:8px;padding:0 14px">
|
||||
<span style="font-size:11px;color:var(--t3)">ROLA:</span>
|
||||
<select id="g-role" onchange="setRole(this.value)" style="background:var(--bg3);border:1px solid var(--rim);color:var(--t1);padding:4px 8px;border-radius:4px;font-size:12px">
|
||||
<option value="pgz_admin">pgz_admin (full)</option>
|
||||
<option value="klub_admin">klub_admin (sve osim OIB)</option>
|
||||
<option value="savez_admin">savez_admin (samo napomena)</option>
|
||||
<option value="klub_trener">klub_trener (sport polja)</option>
|
||||
<option value="sportas">sportas (kontakt + slika)</option>
|
||||
<option value="viewer">viewer (read-only)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div id="page-clanarine" class="page"></div>
|
||||
<div id="page-clanovi" class="page"></div>
|
||||
<div id="page-clanarine" class="page" style="display:none"></div>
|
||||
<div id="page-lijecnicki" class="page" style="display:none"></div>
|
||||
<div id="page-obrasci" class="page" style="display:none"></div>
|
||||
</div>
|
||||
@@ -207,9 +220,33 @@ function closeModal() {
|
||||
$('#modal').innerHTML = '';
|
||||
}
|
||||
|
||||
// Globalna rola (postavlja se preko dropdowna u topbaru)
|
||||
let CURRENT_ROLE = localStorage.getItem('crm-role') || 'pgz_admin';
|
||||
|
||||
function setRole(r) {
|
||||
CURRENT_ROLE = r;
|
||||
localStorage.setItem('crm-role', r);
|
||||
toast('Rola postavljena: ' + r);
|
||||
// ako je otvoren panel, refreshaj edit dozvole
|
||||
if (window._OPEN_PANEL_CID) loadClanPanel(window._OPEN_PANEL_CID);
|
||||
}
|
||||
|
||||
// Wrapper za API koji dodaje X-Role
|
||||
async function apiR(path, opts={}) {
|
||||
const o = Object.assign({headers: {'Content-Type':'application/json', 'X-Role': CURRENT_ROLE}}, opts);
|
||||
if (o.body && typeof o.body !== 'string') o.body = JSON.stringify(o.body);
|
||||
const r = await fetch(API + path, o);
|
||||
if (!r.ok) {
|
||||
const msg = await r.text().catch(()=>r.statusText);
|
||||
throw new Error(`HTTP ${r.status}: ${msg.substring(0,200)}`);
|
||||
}
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function setTab(name) {
|
||||
$$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === name));
|
||||
$$('.page').forEach(p => p.style.display = (p.id === 'page-' + name) ? 'block' : 'none');
|
||||
if (name === 'clanovi') loadClanovi();
|
||||
if (name === 'clanarine') loadClanarine();
|
||||
if (name === 'lijecnicki') loadLijecnicki();
|
||||
if (name === 'obrasci') loadObrasci();
|
||||
|
||||
Reference in New Issue
Block a user