Sichert den bisher uncommitteten Produktionsstand des Kundenbereich-Servers, damit kuenftige Deploys (git reset --hard) nichts mehr verwerfen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
259 lines
7.5 KiB
JavaScript
259 lines
7.5 KiB
JavaScript
import { config } from '../config.js'
|
||
import {
|
||
createAppwriteUser,
|
||
createDocument,
|
||
deleteDocument,
|
||
getCustomerByEmail,
|
||
getDocument,
|
||
getPortalAccessByCustomerId,
|
||
listDocuments,
|
||
updateAppwriteUserEmail,
|
||
updateAppwriteUserPassword,
|
||
deleteAppwriteUser,
|
||
updateDocument,
|
||
Query,
|
||
} from './appwriteAdmin.js'
|
||
|
||
const PORTAL_PASSWORD_PREFIX = '__portalPassword__:'
|
||
|
||
function readPortalPassword(customer) {
|
||
if (customer?.portalPassword) return customer.portalPassword
|
||
const notes = customer?.notes || ''
|
||
const match = notes.match(/^__portalPassword__:([^\n]+)/)
|
||
return match ? match[1] : ''
|
||
}
|
||
|
||
function notesWithPortalPassword(existingNotes, password) {
|
||
const stripped = (existingNotes || '').replace(/^__portalPassword__:[^\n]*\n?/, '').trim()
|
||
if (!password) return stripped || ''
|
||
const line = `${PORTAL_PASSWORD_PREFIX}${password}`
|
||
return stripped ? `${line}\n${stripped}` : line
|
||
}
|
||
|
||
function sanitizeCustomer(customer, { portalPassword } = {}) {
|
||
const storedPassword = portalPassword || readPortalPassword(customer)
|
||
return {
|
||
$id: customer.$id,
|
||
code: customer.code || '',
|
||
name: customer.name || '',
|
||
location: customer.location || '',
|
||
email: customer.email || '',
|
||
phone: customer.phone || '',
|
||
companyName: customer.companyName || '',
|
||
portalAccessEnabled: Boolean(customer.portalAccessEnabled),
|
||
appwriteUserId: customer.appwriteUserId || '',
|
||
customerStatus: customer.customerStatus || '',
|
||
portalPassword: storedPassword || '',
|
||
}
|
||
}
|
||
|
||
async function ensurePortalAccess(customerId, appwriteUserId) {
|
||
const now = new Date().toISOString()
|
||
let portalAccess = await getPortalAccessByCustomerId(customerId)
|
||
if (portalAccess) {
|
||
portalAccess = await updateDocument(config.collections.customerPortalAccess, portalAccess.$id, {
|
||
enabled: true,
|
||
appwriteUserId,
|
||
passwordSet: true,
|
||
})
|
||
} else {
|
||
portalAccess = await createDocument(config.collections.customerPortalAccess, {
|
||
customerId,
|
||
enabled: true,
|
||
appwriteUserId,
|
||
passwordSet: true,
|
||
})
|
||
}
|
||
return portalAccess
|
||
}
|
||
|
||
export async function createCustomerWithPortalAccess(payload) {
|
||
const {
|
||
code = '',
|
||
name,
|
||
location = '',
|
||
email,
|
||
phone = '',
|
||
password,
|
||
} = payload || {}
|
||
|
||
if (!name?.trim()) {
|
||
const error = new Error('Name ist erforderlich')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
if (!email?.trim()) {
|
||
const error = new Error('E-Mail ist erforderlich')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
if (!password || password.length < 8) {
|
||
const error = new Error('Passwort muss mindestens 8 Zeichen haben')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
|
||
const existing = await getCustomerByEmail(email.trim())
|
||
if (existing) {
|
||
const error = new Error('Ein Kunde mit dieser E-Mail existiert bereits')
|
||
error.status = 409
|
||
throw error
|
||
}
|
||
|
||
const appwriteUser = await createAppwriteUser({
|
||
email: email.trim(),
|
||
password,
|
||
name: name.trim(),
|
||
})
|
||
|
||
const now = new Date().toISOString()
|
||
let customer
|
||
try {
|
||
customer = await createDocument(config.collections.customers, {
|
||
code: code.trim(),
|
||
name: name.trim(),
|
||
location: location.trim(),
|
||
email: email.trim(),
|
||
phone: phone.trim(),
|
||
notes: notesWithPortalPassword('', password),
|
||
portalAccessEnabled: true,
|
||
appwriteUserId: appwriteUser.$id,
|
||
customerStatus: 'active',
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
})
|
||
await ensurePortalAccess(customer.$id, appwriteUser.$id)
|
||
} catch (err) {
|
||
try {
|
||
await deleteAppwriteUser(appwriteUser.$id)
|
||
} catch {
|
||
/* ignore rollback failure */
|
||
}
|
||
throw err
|
||
}
|
||
|
||
return {
|
||
customer: sanitizeCustomer(customer, { portalPassword: password }),
|
||
}
|
||
}
|
||
|
||
export async function updateCustomerWithPortalAccess(customerId, payload) {
|
||
const customer = await getDocument(config.collections.customers, customerId).catch(() => null)
|
||
if (!customer) {
|
||
const error = new Error('Kunde nicht gefunden')
|
||
error.status = 404
|
||
throw error
|
||
}
|
||
|
||
const {
|
||
code,
|
||
name,
|
||
location,
|
||
email,
|
||
phone,
|
||
password,
|
||
} = payload || {}
|
||
|
||
const nextEmail = (email ?? customer.email ?? '').trim()
|
||
if (!nextEmail) {
|
||
const error = new Error('E-Mail ist erforderlich')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
if (password && password.length < 8) {
|
||
const error = new Error('Passwort muss mindestens 8 Zeichen haben')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
|
||
const normalizedPassword =
|
||
typeof password === 'string' && password.trim().length > 0 ? password : undefined
|
||
|
||
if (nextEmail.toLowerCase() !== (customer.email || '').trim().toLowerCase()) {
|
||
const existing = await getCustomerByEmail(nextEmail)
|
||
if (existing && existing.$id !== customerId) {
|
||
const error = new Error('Ein anderer Kunde nutzt diese E-Mail bereits')
|
||
error.status = 409
|
||
throw error
|
||
}
|
||
}
|
||
|
||
let appwriteUserId = customer.appwriteUserId || ''
|
||
let portalPassword
|
||
|
||
if (!appwriteUserId && nextEmail && normalizedPassword) {
|
||
const appwriteUser = await createAppwriteUser({
|
||
email: nextEmail,
|
||
password: normalizedPassword,
|
||
name: (name ?? customer.name ?? nextEmail).trim(),
|
||
})
|
||
appwriteUserId = appwriteUser.$id
|
||
portalPassword = normalizedPassword
|
||
} else if (appwriteUserId) {
|
||
if (nextEmail && nextEmail !== customer.email) {
|
||
await updateAppwriteUserEmail(appwriteUserId, nextEmail)
|
||
}
|
||
if (normalizedPassword) {
|
||
await updateAppwriteUserPassword(appwriteUserId, normalizedPassword)
|
||
portalPassword = normalizedPassword
|
||
}
|
||
} else if (normalizedPassword) {
|
||
const error = new Error('Portal-Login fehlt <20> bitte E-Mail setzen und Passwort erneut speichern')
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
|
||
const now = new Date().toISOString()
|
||
const nextPortalPassword =
|
||
normalizedPassword || readPortalPassword(customer) || undefined
|
||
|
||
const updated = await updateDocument(config.collections.customers, customerId, {
|
||
...(code !== undefined ? { code: String(code).trim() } : {}),
|
||
...(name !== undefined ? { name: String(name).trim() } : {}),
|
||
...(location !== undefined ? { location: String(location).trim() } : {}),
|
||
email: nextEmail,
|
||
...(phone !== undefined ? { phone: String(phone).trim() } : {}),
|
||
...(nextPortalPassword
|
||
? { notes: notesWithPortalPassword(customer.notes, nextPortalPassword) }
|
||
: {}),
|
||
...(appwriteUserId ? { appwriteUserId, portalAccessEnabled: true, customerStatus: 'active' } : {}),
|
||
updatedAt: now,
|
||
})
|
||
|
||
if (appwriteUserId) {
|
||
await ensurePortalAccess(customerId, appwriteUserId)
|
||
}
|
||
|
||
return {
|
||
customer: sanitizeCustomer(updated, portalPassword ? { portalPassword } : {}),
|
||
}
|
||
}
|
||
|
||
export async function listCustomersForAdmin() {
|
||
const customers = await listDocuments(config.collections.customers, [Query.orderAsc('name')])
|
||
return customers.map((customer) => sanitizeCustomer(customer))
|
||
}
|
||
|
||
export async function deleteCustomerWithPortalAccess(customerId) {
|
||
const customer = await getDocument(config.collections.customers, customerId)
|
||
.catch(() => null)
|
||
if (!customer) {
|
||
const error = new Error('Kunde nicht gefunden')
|
||
error.status = 404
|
||
throw error
|
||
}
|
||
|
||
const portalAccess = await getPortalAccessByCustomerId(customerId)
|
||
if (portalAccess) {
|
||
await deleteDocument(
|
||
config.collections.customerPortalAccess,
|
||
portalAccess.$id
|
||
)
|
||
}
|
||
await deleteDocument(config.collections.customers, customerId)
|
||
if (customer.appwriteUserId) {
|
||
await deleteAppwriteUser(customer.appwriteUserId).catch(() => {})
|
||
}
|
||
return { success: true }
|
||
}
|