const { getClientToken } = require('./graph.service'); const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); const DB_PATH = process.env.DATABASE_PATH || './database.sqlite'; async function syncEntraProfiles() { const db = new Database(DB_PATH); const startedAt = new Date().toISOString(); // Log starten const logId = db.prepare(` INSERT INTO cron_logs (job_name, started_at, status) VALUES (?, ?, 'running') `).run('entra_profile_sync', startedAt).lastInsertRowid; try { const token = await getClientToken(); const users = db.prepare(`SELECT id, azure_id, username FROM users WHERE azure_id IS NOT NULL AND azure_id != ''`).all(); let synced = 0, errors = 0, photos = 0; for (const user of users) { try { // Profildaten holen const profileRes = await fetch( `https://graph.microsoft.com/v1.0/users/${user.azure_id}?$select=jobTitle,department,mobilePhone,businessPhones,officeLocation,employeeHireDate,displayName,mail,givenName,surname`, { headers: { Authorization: `Bearer ${token}` } } ); if (profileRes.ok) { const p = await profileRes.json(); const phone = p.mobilePhone || (p.businessPhones?.[0]) || null; db.prepare(` UPDATE users SET position = COALESCE(?, position), department = COALESCE(?, department), phone = COALESCE(?, phone), location = COALESCE(?, location), joined_date = COALESCE(?, joined_date), first_name = COALESCE(?, first_name), last_name = COALESCE(?, last_name) WHERE id = ? `).run( p.jobTitle || null, p.department || null, phone, p.officeLocation || null, p.employeeHireDate ? p.employeeHireDate.split('T')[0] : null, p.givenName || null, p.surname || null, user.id ); synced++; } // Foto holen const photoRes = await fetch( `https://graph.microsoft.com/v1.0/users/${user.azure_id}/photo/$value`, { headers: { Authorization: `Bearer ${token}` } } ); if (photoRes.ok) { const avatarDir = path.join(process.env.UPLOAD_PATH || path.join(__dirname, '../../uploads'), 'avatars'); if (!fs.existsSync(avatarDir)) fs.mkdirSync(avatarDir, { recursive: true }); const buffer = Buffer.from(await photoRes.arrayBuffer()); const filename = `${user.azure_id}.jpg`; fs.writeFileSync(path.join(avatarDir, filename), buffer); db.prepare(`UPDATE users SET avatar_url = ? WHERE id = ?`) .run(`/uploads/avatars/${filename}`, user.id); photos++; } } catch (err) { console.error(`[Entra Sync] Fehler bei User ${user.username}:`, err.message); errors++; } } const message = `${synced} Profile synchronisiert, ${photos} Fotos gespeichert, ${errors} Fehler`; db.prepare(`UPDATE cron_logs SET status='success', finished_at=?, message=?, details=? WHERE id=?`) .run(new Date().toISOString(), message, JSON.stringify({ synced, photos, errors, total: users.length }), logId); console.log(`[Entra Sync] ${message}`); return { synced, photos, errors }; } catch (err) { db.prepare(`UPDATE cron_logs SET status='error', finished_at=?, message=? WHERE id=?`) .run(new Date().toISOString(), err.message, logId); throw err; } finally { db.close(); } } module.exports = { syncEntraProfiles };