- 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>
175 lines
5.9 KiB
JavaScript
175 lines
5.9 KiB
JavaScript
import { config } from '../config.js'
|
|
import {
|
|
createAppwriteUser,
|
|
createDocument,
|
|
deleteAppwriteUser,
|
|
deleteDocument,
|
|
getDocument,
|
|
getUserById,
|
|
listDocuments,
|
|
updateAppwriteUserEmail,
|
|
updateAppwriteUserLabels,
|
|
updateAppwriteUserName,
|
|
updateAppwriteUserPassword,
|
|
updateDocument,
|
|
Query,
|
|
} from './appwriteAdmin.js'
|
|
import { ADMIN_LABEL, STAFF_LABEL, isAdminAccount, isStaffAccount } from './staffLabels.js'
|
|
|
|
function httpError(status, message) {
|
|
const error = new Error(message)
|
|
error.status = status
|
|
return error
|
|
}
|
|
|
|
function normalizeEmail(value) {
|
|
return String(value || '').trim().toLowerCase()
|
|
}
|
|
|
|
function normalizeShortcode(value) {
|
|
return String(value || '').trim().toUpperCase().slice(0, 10)
|
|
}
|
|
|
|
function sanitizeEmployee(employee, user) {
|
|
return {
|
|
$id: employee.$id,
|
|
userId: employee.userId,
|
|
displayName: employee.displayName || user?.name || '',
|
|
email: employee.email || user?.email || '',
|
|
shortcode: employee.shortcode || '',
|
|
isAdmin: isAdminAccount(user),
|
|
}
|
|
}
|
|
|
|
async function employeeWithUser(employee) {
|
|
const user = await getUserById(employee.userId).catch(() => null)
|
|
return sanitizeEmployee(employee, user)
|
|
}
|
|
|
|
// Neue Mitarbeiter bekommen das Label "mitarbeiter" (Zugang zum Ticketsystem, siehe staffLabels.js). Beim
|
|
// Bearbeiten nie automatisch: ein employees-Eintrag kann von einem Kunden stammen (frueher selbst anlegbar);
|
|
// Zugang fuer Bestandskonten nur per scripts/mitarbeiter-zugang.mjs --label
|
|
async function setStaffLabels(userId, { isAdmin, grantStaff = false }) {
|
|
const user = await getUserById(userId)
|
|
const labels = new Set(user.labels || [])
|
|
if (grantStaff) labels.add(STAFF_LABEL)
|
|
if (isAdmin === true) labels.add(ADMIN_LABEL)
|
|
if (isAdmin === false) labels.delete(ADMIN_LABEL)
|
|
await updateAppwriteUserLabels(userId, [...labels])
|
|
}
|
|
|
|
export async function listEmployeesForAdmin() {
|
|
const employees = await listDocuments(config.collections.employees, [
|
|
Query.orderAsc('displayName'),
|
|
Query.limit(500),
|
|
])
|
|
return Promise.all(employees.map(employeeWithUser))
|
|
}
|
|
|
|
export async function createEmployeeWithLogin(payload) {
|
|
const displayName = String(payload?.displayName || '').trim()
|
|
const email = normalizeEmail(payload?.email)
|
|
const password = String(payload?.password || '')
|
|
const shortcode = normalizeShortcode(payload?.shortcode)
|
|
const isAdmin = Boolean(payload?.isAdmin)
|
|
|
|
if (!displayName) throw httpError(400, 'Name ist erforderlich')
|
|
if (!email) throw httpError(400, 'E-Mail ist erforderlich')
|
|
if (password.length < 8) {
|
|
throw httpError(400, 'Passwort muss mindestens 8 Zeichen haben')
|
|
}
|
|
|
|
const user = await createAppwriteUser({ email, password, name: displayName })
|
|
let employee
|
|
try {
|
|
await setStaffLabels(user.$id, { isAdmin, grantStaff: true })
|
|
employee = await createDocument(config.collections.employees, {
|
|
userId: user.$id,
|
|
displayName,
|
|
email,
|
|
shortcode,
|
|
})
|
|
} catch (error) {
|
|
await deleteAppwriteUser(user.$id).catch(() => {})
|
|
throw error
|
|
}
|
|
|
|
return { employee: await employeeWithUser(employee) }
|
|
}
|
|
|
|
export async function updateEmployeeWithLogin(employeeId, payload, actorUserId) {
|
|
const employee = await getDocument(config.collections.employees, employeeId)
|
|
.catch(() => null)
|
|
if (!employee) throw httpError(404, 'Mitarbeiter nicht gefunden')
|
|
|
|
const displayName = payload?.displayName === undefined
|
|
? employee.displayName
|
|
: String(payload.displayName || '').trim()
|
|
const email = payload?.email === undefined
|
|
? normalizeEmail(employee.email)
|
|
: normalizeEmail(payload.email)
|
|
const shortcode = payload?.shortcode === undefined
|
|
? employee.shortcode || ''
|
|
: normalizeShortcode(payload.shortcode)
|
|
const password = typeof payload?.password === 'string' ? payload.password : ''
|
|
|
|
if (
|
|
employee.userId === actorUserId &&
|
|
payload?.isAdmin === false
|
|
) {
|
|
throw httpError(400, 'Die eigene Admin-Berechtigung kann nicht entfernt werden')
|
|
}
|
|
|
|
if (!displayName) throw httpError(400, 'Name ist erforderlich')
|
|
if (!email) throw httpError(400, 'E-Mail ist erforderlich')
|
|
if (password && password.length < 8) {
|
|
throw httpError(400, 'Passwort muss mindestens 8 Zeichen haben')
|
|
}
|
|
|
|
const user = await getUserById(employee.userId)
|
|
if (email !== normalizeEmail(user.email)) {
|
|
await updateAppwriteUserEmail(employee.userId, email)
|
|
}
|
|
if (displayName !== (user.name || '')) {
|
|
await updateAppwriteUserName(employee.userId, displayName)
|
|
}
|
|
if (password) {
|
|
await updateAppwriteUserPassword(employee.userId, password)
|
|
}
|
|
if (payload?.isAdmin !== undefined) {
|
|
await setStaffLabels(employee.userId, { isAdmin: Boolean(payload.isAdmin) })
|
|
}
|
|
|
|
const updated = await updateDocument(config.collections.employees, employeeId, {
|
|
displayName,
|
|
email,
|
|
shortcode,
|
|
})
|
|
return { employee: await employeeWithUser(updated) }
|
|
}
|
|
|
|
export async function deleteEmployeeWithLogin(employeeId, actorUserId) {
|
|
const employee = await getDocument(config.collections.employees, employeeId)
|
|
.catch(() => null)
|
|
if (!employee) throw httpError(404, 'Mitarbeiter nicht gefunden')
|
|
if (employee.userId === actorUserId) {
|
|
throw httpError(400, 'Der eigene angemeldete Account kann nicht gelöscht werden')
|
|
}
|
|
|
|
// Konto ohne Mitarbeiter-Label (z. B. Kunde mit altem employees-Eintrag): nur den Eintrag loeschen, das Konto ist
|
|
// sein Portal-Login. Konto nicht lesbar -> abbrechen statt zu raten
|
|
const user = employee.userId
|
|
? await getUserById(employee.userId).catch((err) => {
|
|
if (err.status === 404) return null
|
|
throw err
|
|
})
|
|
: null
|
|
await deleteDocument(config.collections.employees, employeeId)
|
|
if (!isStaffAccount(user)) return { success: true, accountKept: Boolean(user) }
|
|
await deleteAppwriteUser(employee.userId).catch((error) => {
|
|
error.message = `Mitarbeiter entfernt, Login konnte nicht gelöscht werden: ${error.message}`
|
|
throw error
|
|
})
|
|
return { success: true }
|
|
}
|