import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); dotenv.config({ path: resolve(__dirname, '..', '.env') }); import express from 'express'; import cors from 'cors'; import { Client, Users, Teams, Databases, ID, Query } from 'node-appwrite'; const ENDPOINT = process.env.APPWRITE_ENDPOINT; const PROJECT_ID = process.env.VITE_APPWRITE_PROJECT_ID; const API_KEY = process.env.APPWRITE_API_KEY; const DATABASE_ID = process.env.VITE_APPWRITE_DATABASE_ID || 'defekttrack_db'; const ADMIN_SECRET = process.env.ADMIN_SECRET; const TEAM_ROLES = ['admin', 'firmenleiter', 'filialleiter', 'service', 'lager']; // #region agent log function debugLog(location, message, data, hypothesisId) { const payload = { sessionId: '405dbc', location, message, data: data || {}, timestamp: Date.now(), hypothesisId }; fetch('http://127.0.0.1:7886/ingest/990166f5-529c-4789-bcc2-9ebbe976f059', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '405dbc' }, body: JSON.stringify(payload) }).catch(() => {}); } // #endregion const app = express(); app.use(cors({ origin: true, credentials: true })); app.use(express.json()); function requireAdminSecret(req, res, next) { const secret = req.headers['x-admin-secret']; if (!ADMIN_SECRET || secret !== ADMIN_SECRET) { // #region agent log debugLog('server/index.js:middleware', '403 admin secret mismatch', { hasServerSecret: !!ADMIN_SECRET, hasHeader: !!secret, headerLength: typeof secret === 'string' ? secret.length : 0 }, 'C'); // #endregion return res.status(403).json({ error: 'Forbidden – ADMIN_SECRET und VITE_ADMIN_SECRET in .env müssen identisch sein. Beide Dev-Server (Vite + npm run dev:api) nach .env-Änderung neu starten.' }); } next(); } app.post('/api/admin/create-user', requireAdminSecret, async (req, res) => { // #region agent log debugLog('server/index.js:route', 'create-user route hit', { bodyKeys: Object.keys(req.body || {}) }, 'A'); // #endregion try { const { email, password, name, locationId, role, mustChangePassword } = req.body || {}; if (!email || !password || !name || !locationId || !role) { return res.status(400).json({ error: 'Fehlende Felder', required: ['email', 'password', 'name', 'locationId', 'role'], }); } if (!TEAM_ROLES.includes(role)) { return res.status(400).json({ error: 'Ungültige Rolle', allowed: TEAM_ROLES, }); } if (!ENDPOINT || !PROJECT_ID || !API_KEY) { // #region agent log debugLog('server/index.js:env', 'env missing', { hasEndpoint: !!ENDPOINT, hasProjectId: !!PROJECT_ID, hasApiKey: !!API_KEY }, 'B'); // #endregion return res.status(500).json({ error: 'Server-Konfiguration unvollständig (APPWRITE_ENDPOINT, VITE_APPWRITE_PROJECT_ID, APPWRITE_API_KEY in .env prüfen)' }); } // #region agent log debugLog('server/index.js:env', 'env check passed', { hasEndpoint: true, hasProjectId: true, hasApiKey: true }, 'B'); // #endregion const client = new Client().setEndpoint(ENDPOINT).setProject(PROJECT_ID).setKey(API_KEY); const users = new Users(client); const teams = new Teams(client); const databases = new Databases(client); let userId; // #region agent log debugLog('server/index.js:users', 'before users.create', { email: !!email, role }, 'D'); // #endregion try { const user = await users.create(ID.unique(), email, undefined, password, name); userId = user.$id; } catch (err) { // #region agent log debugLog('server/index.js:users', 'users.create failed', { code: err.code, message: err.message }, 'D'); // #endregion if (err.code === 409) { const list = await users.list([Query.equal('email', [email])]); if (list.users.length > 0) userId = list.users[0].$id; } if (!userId) { return res.status(400).json({ error: err.message || 'User anlegen fehlgeschlagen' }); } } try { await teams.createMembership(role, [], email, userId, undefined, `${ENDPOINT}/auth/confirm`); } catch (err) { if (err.code !== 409) { console.warn('Team-Membership:', err.message); } } try { await databases.createDocument(DATABASE_ID, 'users_meta', ID.unique(), { userId, locationId: locationId || '', userName: name, role, mustChangePassword: mustChangePassword !== false, }); } catch (err) { if (err.code !== 409) { console.warn('users_meta:', err.message); } } return res.status(201).json({ userId, email, name, role, locationId }); } catch (err) { // #region agent log debugLog('server/index.js:outer', 'outer catch 500', { message: err.message, code: err?.code, name: err?.name }, 'E'); // #endregion console.error('create-user error:', err); const message = err.message || err.toString?.() || 'Interner Serverfehler'; return res.status(500).json({ error: message }); } }); app.patch('/api/admin/update-user', requireAdminSecret, async (req, res) => { try { const { userId: targetUserId, userName, locationId, role: newRole } = req.body || {}; if (!targetUserId) { return res.status(400).json({ error: 'userId erforderlich' }); } if (!ENDPOINT || !PROJECT_ID || !API_KEY) { return res.status(500).json({ error: 'Server-Konfiguration unvollständig' }); } const client = new Client().setEndpoint(ENDPOINT).setProject(PROJECT_ID).setKey(API_KEY); const users = new Users(client); const teams = new Teams(client); const db = new Databases(client); const metaRes = await db.listDocuments(DATABASE_ID, 'users_meta', [ Query.equal('userId', [targetUserId]), Query.limit(1), ]); const metaDoc = metaRes.documents[0]; if (!metaDoc) { return res.status(404).json({ error: 'Benutzer nicht gefunden' }); } const updates = {}; const newUserName = userName !== undefined ? String(userName).trim() : null; if (newUserName !== null) updates.userName = newUserName; if (locationId !== undefined) updates.locationId = locationId || ''; if (newUserName && newUserName !== metaDoc.userName) { const assetsRes = await db.listDocuments(DATABASE_ID, 'assets', [ Query.equal('zustaendig', [metaDoc.userName]), Query.limit(500), ]); for (const a of assetsRes.documents) { await db.updateDocument(DATABASE_ID, 'assets', a.$id, { zustaendig: newUserName }); } } if (newRole !== undefined) { if (!TEAM_ROLES.includes(newRole)) { return res.status(400).json({ error: 'Ungültige Rolle', allowed: TEAM_ROLES }); } updates.role = newRole; const appUser = await users.get(targetUserId); const email = appUser.email; for (const teamId of TEAM_ROLES) { try { const list = await teams.listMemberships(teamId, [Query.limit(100)]); const membership = list.memberships.find((m) => m.userId === targetUserId); if (membership) { await teams.deleteMembership(teamId, membership.$id); } } catch (e) { if (e.code !== 404) console.warn('deleteMembership:', e.message); } } try { await teams.createMembership(newRole, [], email, targetUserId, undefined, `${ENDPOINT}/auth/confirm`); } catch (err) { if (err.code !== 409) console.warn('createMembership:', err.message); } } if (Object.keys(updates).length > 0) { if (updates.userName && metaDoc.userName) { try { const assetsRes = await db.listDocuments(DATABASE_ID, 'assets', [ Query.equal('zustaendig', [metaDoc.userName]), Query.limit(500), ]); for (const a of assetsRes.documents) { await db.updateDocument(DATABASE_ID, 'assets', a.$id, { zustaendig: updates.userName, }); } } catch (e) { console.warn('Asset zustaendig-Update:', e.message); } } await db.updateDocument(DATABASE_ID, 'users_meta', metaDoc.$id, updates); } return res.status(200).json({ userId: targetUserId, ...updates }); } catch (err) { console.error('update-user error:', err); return res.status(500).json({ error: err.message || 'Interner Serverfehler' }); } }); const PORT = process.env.API_PORT || 3001; app.listen(PORT, () => { console.log(`API server http://localhost:${PORT}`); });