Add: Entra Cronjob, Cronjob-Verwaltung, Profilfotos, PDF-Auth-Fix, Tickets-Demo
This commit is contained in:
99
backend/src/services/entraSync.service.js
Normal file
99
backend/src/services/entraSync.service.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user