So mit neuen UI Ideen und so
Dieser Commit ist enthalten in:
35
backend/src/services/auditService.ts
Normale Datei
35
backend/src/services/auditService.ts
Normale Datei
@ -0,0 +1,35 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { db } from '../config/secureDatabase'
|
||||
import { logger } from '../utils/logger'
|
||||
import type { Request } from 'express'
|
||||
|
||||
export function logSecurityAudit(
|
||||
action: 'create' | 'read' | 'update' | 'delete' | 'login' | 'logout' | 'failed_login',
|
||||
entityType: string,
|
||||
entityId: string,
|
||||
userId: string,
|
||||
req: Request,
|
||||
riskLevel: 'low' | 'medium' | 'high' | 'critical' = 'low'
|
||||
) {
|
||||
try {
|
||||
db.prepare(`
|
||||
INSERT INTO security_audit_log (
|
||||
id, entity_type, entity_id, action, user_id,
|
||||
timestamp, ip_address, user_agent, risk_level
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
uuidv4(),
|
||||
entityType,
|
||||
entityId,
|
||||
action,
|
||||
userId,
|
||||
new Date().toISOString(),
|
||||
(req as any).ip || (req as any).connection?.remoteAddress,
|
||||
req.get('user-agent'),
|
||||
riskLevel
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error('Failed to log security audit:', error)
|
||||
}
|
||||
}
|
||||
|
||||
123
backend/src/services/sync/applier.ts
Normale Datei
123
backend/src/services/sync/applier.ts
Normale Datei
@ -0,0 +1,123 @@
|
||||
import { db } from '../../config/database'
|
||||
|
||||
export async function applyChanges(payload: any) {
|
||||
const { type, action, data } = payload
|
||||
switch (type) {
|
||||
case 'employees':
|
||||
await syncEmployee(action, data)
|
||||
break
|
||||
case 'skills':
|
||||
await syncSkill(action, data)
|
||||
break
|
||||
case 'users':
|
||||
await syncUser(action, data)
|
||||
break
|
||||
case 'settings':
|
||||
await syncSettings(action, data)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function syncEmployee(action: string, data: any) {
|
||||
switch (action) {
|
||||
case 'create':
|
||||
db.prepare(`
|
||||
INSERT INTO employees (
|
||||
id, first_name, last_name, employee_number, photo, position,
|
||||
department, email, phone, mobile, office, availability,
|
||||
clearance_level, clearance_valid_until, clearance_issued_date,
|
||||
created_at, updated_at, created_by
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
data.id, data.firstName, data.lastName, data.employeeNumber,
|
||||
data.photo, data.position, data.department, data.email,
|
||||
data.phone, data.mobile, data.office, data.availability,
|
||||
data.clearance?.level, data.clearance?.validUntil,
|
||||
data.clearance?.issuedDate, data.createdAt, data.updatedAt,
|
||||
data.createdBy
|
||||
)
|
||||
if (data.skills) {
|
||||
for (const skill of data.skills) {
|
||||
db.prepare(`
|
||||
INSERT INTO employee_skills (employee_id, skill_id, level, verified, verified_by, verified_date)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
data.id, skill.id, skill.level,
|
||||
skill.verified ? 1 : 0, skill.verifiedBy, skill.verifiedDate
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'update':
|
||||
db.prepare(`
|
||||
UPDATE employees SET
|
||||
first_name = ?, last_name = ?, position = ?, department = ?,
|
||||
email = ?, phone = ?, mobile = ?, office = ?, availability = ?,
|
||||
updated_at = ?, updated_by = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
data.firstName, data.lastName, data.position, data.department,
|
||||
data.email, data.phone, data.mobile, data.office, data.availability,
|
||||
data.updatedAt, data.updatedBy, data.id
|
||||
)
|
||||
break
|
||||
case 'delete':
|
||||
db.prepare('DELETE FROM employees WHERE id = ?').run(data.id)
|
||||
db.prepare('DELETE FROM employee_skills WHERE employee_id = ?').run(data.id)
|
||||
db.prepare('DELETE FROM language_skills WHERE employee_id = ?').run(data.id)
|
||||
db.prepare('DELETE FROM specializations WHERE employee_id = ?').run(data.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function syncSkill(action: string, data: any) {
|
||||
switch (action) {
|
||||
case 'create':
|
||||
db.prepare(`
|
||||
INSERT INTO skills (id, name, category, description, requires_certification, expires_after)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(data.id, data.name, data.category, data.description || null, data.requiresCertification ? 1 : 0, data.expiresAfter || null)
|
||||
break
|
||||
case 'update':
|
||||
db.prepare(`
|
||||
UPDATE skills SET name = COALESCE(?, name), category = COALESCE(?, category), description = COALESCE(?, description)
|
||||
WHERE id = ?
|
||||
`).run(data.name || null, data.category || null, data.description || null, data.id)
|
||||
break
|
||||
case 'delete':
|
||||
db.prepare('DELETE FROM employee_skills WHERE skill_id = ?').run(data.id)
|
||||
db.prepare('DELETE FROM skills WHERE id = ?').run(data.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function syncUser(action: string, data: any) {
|
||||
switch (action) {
|
||||
case 'create':
|
||||
db.prepare(`
|
||||
INSERT INTO users (id, username, email, password, role, employee_id, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(data.id, data.username, data.email, data.password, data.role, data.employeeId || null, 1, data.createdAt, data.updatedAt)
|
||||
break
|
||||
case 'update':
|
||||
db.prepare(`
|
||||
UPDATE users SET username = ?, email = ?, role = ?, employee_id = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`).run(data.username, data.email, data.role, data.employeeId || null, data.updatedAt, data.id)
|
||||
break
|
||||
case 'delete':
|
||||
db.prepare('DELETE FROM users WHERE id = ?').run(data.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function syncSettings(action: string, data: any) {
|
||||
if (action === 'update') {
|
||||
for (const [key, value] of Object.entries(data || {})) {
|
||||
db.prepare(`INSERT INTO system_settings (key, value, updated_at) VALUES (?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
|
||||
.run(key, String(value), new Date().toISOString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
60
backend/src/services/sync/queueStore.ts
Normale Datei
60
backend/src/services/sync/queueStore.ts
Normale Datei
@ -0,0 +1,60 @@
|
||||
import { db } from '../../config/database'
|
||||
|
||||
export const queueStore = {
|
||||
getPending() {
|
||||
return db.prepare(`
|
||||
SELECT * FROM sync_log
|
||||
WHERE status = 'pending'
|
||||
ORDER BY created_at ASC
|
||||
`).all() as any[]
|
||||
},
|
||||
markCompleted(id: string) {
|
||||
db.prepare(`UPDATE sync_log SET status = 'completed', completed_at = ? WHERE id = ?`).run(new Date().toISOString(), id)
|
||||
},
|
||||
markFailed(id: string, message: string) {
|
||||
db.prepare(`UPDATE sync_log SET status = 'failed', error_message = ? WHERE id = ?`).run(message, id)
|
||||
},
|
||||
updateMetadata(nodeId: string, result: { success: boolean; syncedItems: number; conflicts: any[]; errors: any[] }) {
|
||||
const existing = db.prepare('SELECT * FROM sync_metadata WHERE node_id = ?').get(nodeId) as any
|
||||
if (existing) {
|
||||
db.prepare(`
|
||||
UPDATE sync_metadata SET
|
||||
last_sync_at = ?,
|
||||
last_successful_sync = ?,
|
||||
total_synced_items = total_synced_items + ?,
|
||||
total_conflicts = total_conflicts + ?,
|
||||
total_errors = total_errors + ?
|
||||
WHERE node_id = ?
|
||||
`).run(
|
||||
new Date().toISOString(),
|
||||
result.success ? new Date().toISOString() : existing.last_successful_sync,
|
||||
result.syncedItems,
|
||||
result.conflicts.length,
|
||||
result.errors.length,
|
||||
nodeId
|
||||
)
|
||||
} else {
|
||||
db.prepare(`
|
||||
INSERT INTO sync_metadata (
|
||||
node_id, last_sync_at, last_successful_sync,
|
||||
total_synced_items, total_conflicts, total_errors
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
nodeId,
|
||||
new Date().toISOString(),
|
||||
result.success ? new Date().toISOString() : null,
|
||||
result.syncedItems,
|
||||
result.conflicts.length,
|
||||
result.errors.length
|
||||
)
|
||||
}
|
||||
},
|
||||
getSyncSettings() {
|
||||
const settings = db.prepare('SELECT * FROM sync_settings WHERE id = ?').get('default') as any
|
||||
return settings || { autoSyncInterval: 'disabled', conflictResolution: 'admin' }
|
||||
},
|
||||
getNodeInfo(nodeId: string) {
|
||||
return db.prepare('SELECT * FROM network_nodes WHERE id = ?').get(nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
17
backend/src/services/sync/transport.ts
Normale Datei
17
backend/src/services/sync/transport.ts
Normale Datei
@ -0,0 +1,17 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export async function sendToNode(targetNode: any, payload: any, nodeId: string) {
|
||||
const response = await axios.post(
|
||||
`http://${targetNode.ip_address}:${targetNode.port}/api/sync/receive`,
|
||||
payload,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${targetNode.api_key}`,
|
||||
'X-Node-Id': nodeId
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren