Files
Webklar-Kundenbereich/scripts/mitarbeiter-zugang.mjs
T
ClaudeandClaude Opus 5.5 10b393687c Mitarbeiter-Zugang: Review-Punkte (fail closed, Kunden-Konto bleibt, Probelauf robuster)
- removeEmployeeEntry: nur 404 heisst "kein Konto", jeder andere Fehler bricht ab
- Mitarbeiter loeschen (Admin-Panel): Konto ohne Mitarbeiter-Label (Kunde mit
  altem employees-Eintrag) bleibt bestehen, nur der Eintrag geht
- Mitarbeiter bearbeiten gibt nie automatisch das Label (der Eintrag kann von
  einem Kunden stammen); nur Neuanlage bekommt "mitarbeiter"
- --rechte: --offen-lassen <ids>, unlesbare Listen als Fehler statt Abbruch
  (--anwenden verweigert dann), --sicherung <datei> + docker-cp-Hinweis,
  Bucket-Einstellung transformations wird mitgeschickt
- getEmployeeByEmail entfernt (unbenutzt)
- Tests: write/users-unverified, zweiter Lauf idempotent, >100 Dokumente,
  Loeschen mit Kunden- und Mitarbeiter-Konto

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:00:48 +00:00

133 lines
6.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Zugang zum Ticketsystem nur fuer Mitarbeiter (Label "mitarbeiter" oder "admin").
*
* Usage (im Container, mit dem Server-Key):
* docker exec -it -w /app webklar-kundenbereich-server node scripts/mitarbeiter-zugang.mjs --status
* ... --label <userId>[,<userId>...] [--yes] Label "mitarbeiter" vergeben (nur ausdrueckliche Konten)
* ... --eintrag-loeschen <employeeId> [--yes] employees-Eintrag eines Kunden loeschen (Konto bleibt)
* ... --rechte [--offen-lassen <id>[,<id>...]] Probelauf: welche Rechte sich aendern (IDs bleiben offen)
* ... --rechte --anwenden [--sicherung <datei>] [--yes] Rechte umstellen, vorher Sicherung als JSON
* ... --rechte --zurueck <sicherung.json> [--yes] Rechte aus einer Sicherung wiederherstellen
*/
import 'dotenv/config'
import { readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createInterface } from 'node:readline/promises'
import {
applyPermissionChanges,
grantStaffLabel,
planPermissionChanges,
removeEmployeeEntry,
staffAccessStatus,
} from '../server/services/staffAccessAdmin.js'
const args = process.argv.slice(2)
const flag = (name) => {
const i = args.indexOf(name)
return i === -1 ? null : args[i + 1] || ''
}
async function confirm(question) {
if (args.includes('--yes')) return true
const rl = createInterface({ input: process.stdin, output: process.stdout })
const answer = await rl.question(`${question} (Ja/Nein) `)
rl.close()
return /^j(a)?$/i.test(answer.trim())
}
function where(change) {
if (change.kind === 'collection') return `Collection ${change.databaseId}/${change.id} (${change.name})`
if (change.kind === 'document') return `Dokument ${change.collectionId}/${change.id}`
if (change.kind === 'bucket') return `Bucket ${change.id} (${change.name})`
return `Datei ${change.bucketId}/${change.id}`
}
function printChanges(changes) {
for (const change of changes) {
console.log(where(change))
console.log(` vorher: ${change.before.join(' ') || '(keine)'}`)
console.log(` nachher: ${change.after.join(' ') || '(keine)'}`)
}
}
async function status() {
const { rows, labelOnly } = await staffAccessStatus()
console.log('employees-Einträge:')
for (const r of rows) {
const state = r.staff ? 'Zugang: ja ' : 'Zugang: NEIN'
const note = r.customer ? ` ← KUNDE (${r.customer}), Eintrag löschen` : r.login ? '' : ' (kein Konto)'
console.log(` ${r.employeeId.padEnd(22)} ${r.userId.padEnd(38)} ${r.name.padEnd(20)} ${r.email.padEnd(32)} ${state} [${r.labels.join(',')}]${note}`)
}
if (labelOnly.length) {
console.log('Konten mit Mitarbeiter-Label ohne employees-Eintrag:')
for (const u of labelOnly) console.log(` ${u.userId.padEnd(38)} ${u.name.padEnd(20)} ${u.email.padEnd(32)} [${u.labels.join(',')}]`)
}
}
async function label(ids) {
const userIds = ids.split(',').map((id) => id.trim()).filter(Boolean)
if (!userIds.length) throw new Error('Bitte userIds angeben (kommagetrennt).')
console.log(`Label "mitarbeiter" für: ${userIds.join(', ')}`)
if (!(await confirm('Diese Konten bekommen Zugang zum Ticketsystem. Fortfahren?'))) return console.log('Abgebrochen.')
for (const userId of userIds) {
const r = await grantStaffLabel(userId)
console.log(`Zugang: ${r.name} <${r.email}> [${r.labels.join(',')}]`)
}
}
async function removeEntry(employeeId) {
if (!(await confirm(`employees-Eintrag ${employeeId} löschen (das Konto bleibt)?`))) return console.log('Abgebrochen.')
const r = await removeEmployeeEntry(employeeId)
console.log(`Eintrag gelöscht: ${r.name} (Konto ${r.userId} bleibt)`)
}
function report(results) {
const failed = results.filter((r) => !r.ok)
for (const r of failed) console.log(`FEHLER ${where(r.change)}: ${r.error}`)
console.log(`${results.length - failed.length} von ${results.length} Stellen geschrieben.`)
if (failed.length) process.exitCode = 1
}
async function permissions() {
const restore = flag('--zurueck')
if (restore) {
const changes = JSON.parse(readFileSync(restore, 'utf8'))
printChanges(changes.map((c) => ({ ...c, before: c.after, after: c.before })))
if (!(await confirm(`${changes.length} Stellen auf den Stand der Sicherung zurücksetzen?`))) return console.log('Abgebrochen.')
return report(await applyPermissionChanges(changes, { direction: 'before' }))
}
const keep = (flag('--offen-lassen') || '').split(',').map((id) => id.trim()).filter(Boolean)
const { changes, errors } = await planPermissionChanges(undefined, { keep })
for (const e of errors) console.log(`NICHT LESBAR ${e.path}: ${e.error}`)
if (keep.length) console.log(`Bleiben offen: ${keep.join(', ')}`)
if (!changes.length) return console.log('Keine offenen Rechte (any/guests/users) gefunden.')
printChanges(changes)
if (!args.includes('--anwenden')) return console.log(`Probelauf: ${changes.length} Stellen. Mit --anwenden umstellen.`)
if (errors.length) throw new Error('Nicht alles lesbar (siehe oben) - erst den Key-Scope klären, dann erneut.')
const backup = flag('--sicherung') || join(tmpdir(), `mitarbeiter-rechte-${new Date().toISOString().replace(/[:.]/g, '-')}.json`)
writeFileSync(backup, JSON.stringify(changes, null, 2))
console.log(`Sicherung: ${backup}`)
console.log(` aus dem Container holen: docker cp webklar-kundenbereich-server:${backup} /root/reports/`)
if (!(await confirm(`${changes.length} Stellen nur noch für Mitarbeiter freigeben?`))) return console.log('Abgebrochen.')
report(await applyPermissionChanges(changes))
}
async function main() {
if (args.includes('--status')) return status()
const ids = flag('--label')
if (ids !== null) return label(ids)
const employeeId = flag('--eintrag-loeschen')
if (employeeId) return removeEntry(employeeId)
if (args.includes('--rechte')) return permissions()
console.log('Usage: --status | --label <userId>[,...] | --eintrag-loeschen <employeeId> | --rechte [--offen-lassen <id>,...] [--anwenden [--sicherung <datei>] | --zurueck <datei>] [--yes]')
}
main().catch((err) => {
console.error(err.message)
process.exit(1)
})