docs: 添加项目文档和优化后台服务
This commit is contained in:
+74
-53
@@ -320,70 +320,88 @@ const geofence = polygon([
|
||||
}
|
||||
}
|
||||
|
||||
// Worker Clock In/Out Endpoint
|
||||
// Worker Clock In/Out Endpoint - Optimized Version
|
||||
app.post('/api/clock', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId, eventType, qrCodeValue, latitude, longitude, notes } = req.body
|
||||
const { userId, eventType, qrCodeValue, latitude, longitude, notes } = req.body;
|
||||
const connection = await db.getConnection(); // Get connection from pool first
|
||||
|
||||
// Bypass geofence and QR code validation for forced events
|
||||
if (qrCodeValue !== 'FORCE_CLOCK_OUT' && qrCodeValue !== 'BLACKLIST_APP_DETECTED') {
|
||||
// Geofencing check using the directly imported functions
|
||||
const userLocation = point([longitude, latitude]);
|
||||
const isWithinGeofence = booleanPointInPolygon(userLocation, geofence);
|
||||
try {
|
||||
// Start transaction
|
||||
await connection.beginTransaction();
|
||||
|
||||
if (!isWithinGeofence) {
|
||||
// User is outside the geofence, log a 'failed' attempt
|
||||
const distance = pointToLineDistance(userLocation, geofence.geometry.coordinates[0], { units: 'meters' });
|
||||
const notes = `Clock-in outside of the zone: ${distance.toFixed(2)} meters.`;
|
||||
// Bypass checks for special cases
|
||||
if (qrCodeValue !== 'FORCE_CLOCK_OUT') {
|
||||
// Parallelize geofence and QR code checks
|
||||
const [geofenceCheck, qrCheck] = await Promise.all([
|
||||
booleanPointInPolygon(point([longitude, latitude]), geofence),
|
||||
connection.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [qrCodeValue])
|
||||
]);
|
||||
|
||||
await db.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, notes]
|
||||
);
|
||||
if (!geofenceCheck) {
|
||||
const distance = pointToLineDistance(point([longitude, latitude]),
|
||||
geofence.geometry.coordinates[0], { units: 'meters' });
|
||||
const notes = `Clock-in outside of the zone: ${distance.toFixed(2)} meters.`;
|
||||
|
||||
return res.status(403).json({ message: `You are not within the allowed work area.` });
|
||||
await connection.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, notes]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
return res.status(403).json({ message: `You are not within the allowed work area.` });
|
||||
}
|
||||
|
||||
if (qrCheck[0].length === 0) {
|
||||
await connection.rollback();
|
||||
return res.status(400).json({ message: 'Invalid QR Code scanned.' });
|
||||
}
|
||||
|
||||
if (!qrCheck[0][0].is_active) {
|
||||
await connection.rollback();
|
||||
return res.status(400).json({ message: 'This QR Code has expired and is no longer active.' });
|
||||
}
|
||||
}
|
||||
|
||||
const [qrRows] = await db.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [
|
||||
qrCodeValue,
|
||||
]);
|
||||
// Check last event
|
||||
const [lastEvent] = await connection.execute(
|
||||
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (qrRows.length === 0) {
|
||||
return res.status(400).json({ message: 'Invalid QR Code scanned.' });
|
||||
}
|
||||
|
||||
if (!qrRows[0].is_active) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: 'This QR Code has expired and is no longer active.' });
|
||||
}
|
||||
}
|
||||
const [lastEventRows] = await db.execute(
|
||||
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
|
||||
[userId],
|
||||
)
|
||||
if (lastEventRows.length > 0 && lastEventRows[0].event_type === eventType) {
|
||||
if (qrCodeValue === 'FORCE_CLOCK_OUT') {
|
||||
// If it's a forced clock-out on an already clocked-out user, log it as a failed event
|
||||
await db.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, `FAKE GPS APP Detected.`]
|
||||
);
|
||||
return res.status(200).json({ message: 'Forced clock-out attempt on already clocked-out user was logged.' });
|
||||
}
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: `You are already clocked ${eventType === 'clock_in' ? 'in' : 'out'}.` })
|
||||
// If it's a forced clock-out, log it as a failed event regardless of previous state
|
||||
await connection.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, `FAKE GPS APP Detected.`]
|
||||
);
|
||||
await connection.commit();
|
||||
return res.status(200).json({ message: 'Forced clock-out attempt was logged.' });
|
||||
}
|
||||
|
||||
if (lastEvent.length > 0 && lastEvent[0].event_type === eventType) {
|
||||
await connection.rollback();
|
||||
return res.status(400).json({ message: `You are already clocked ${eventType === 'clock_in' ? 'in' : 'out'}.` });
|
||||
}
|
||||
|
||||
// Insert new record
|
||||
const timestamp = new Date();
|
||||
await connection.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, eventType, timestamp, qrCodeValue || null, latitude || null, longitude || null, notes || null]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
res.status(201).json({ message: 'Clock event recorded successfully' });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
throw err;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
const timestamp = new Date()
|
||||
await db.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, eventType, timestamp, qrCodeValue || null, latitude || null, longitude || null, notes || null],
|
||||
)
|
||||
res.status(201).json({ message: 'Clock event recorded successfully' })
|
||||
} catch (error) {
|
||||
console.error('Clock event error:', error)
|
||||
res.status(500).json({ message: 'Database error during clock event.' })
|
||||
console.error('Clock event error:', error);
|
||||
res.status(500).json({ message: 'Database error during clock event.' });
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1006,7 +1024,9 @@ const geofence = polygon([
|
||||
})
|
||||
|
||||
|
||||
// Security Check Endpoint
|
||||
// Security Check Endpoint - COMMENTED OUT FOR SERVER-SIDE SECURITY PREFERENCE
|
||||
// Client-side security computation removed per user preference for server-side security
|
||||
/*
|
||||
app.post('/api/security/check', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId, timestamp, deviceInfo, securityCheck } = req.body
|
||||
@@ -1077,6 +1097,7 @@ const geofence = polygon([
|
||||
res.status(500).json({ message: 'Database error during security check.' })
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
// Get Security Status Endpoint
|
||||
app.get('/api/security/status/:userId', authenticateJWT, async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user