224 lines
8.5 KiB
JavaScript
224 lines
8.5 KiB
JavaScript
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}`);
|
||
});
|