136 lines
6.3 KiB
JavaScript
136 lines
6.3 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)
|
||
* [--ohne-buckets] Buckets auslassen (Key ohne buckets-Scope, dann in der Konsole)
|
||
* ... --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 buckets = !args.includes('--ohne-buckets')
|
||
const { changes, errors } = await planPermissionChanges(undefined, { keep, buckets })
|
||
for (const e of errors) console.log(`NICHT LESBAR ${e.path}: ${e.error}`)
|
||
if (keep.length) console.log(`Bleiben offen: ${keep.join(', ')}`)
|
||
if (!buckets) console.log('Buckets übersprungen – deren Rechte bitte in der Appwrite-Konsole umstellen.')
|
||
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>,...] [--ohne-buckets] [--anwenden [--sicherung <datei>] | --zurueck <datei>] [--yes]')
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err.message)
|
||
process.exit(1)
|
||
})
|