100 lines
4.1 KiB
JavaScript
100 lines
4.1 KiB
JavaScript
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 };
|