fix:connection lost

This commit is contained in:
2025-11-06 10:05:07 +08:00
parent b577d5ad1b
commit 899b6fae93
5 changed files with 463 additions and 394 deletions
+90 -89
View File
@@ -2,6 +2,8 @@ import express from 'express';
import { point, polygon, booleanPointInPolygon, pointToLineDistance } from '@turf/turf';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { getConnection } from './pool.js';
const db = await getConnection();
async function validateDeviceForUser(userId, deviceUuid, db) {
const [userRows] = await db.execute('SELECT device_uuid FROM workers WHERE id = ?', [userId]);
@@ -21,7 +23,7 @@ async function isClockingEnabled(conn) {
return rows.length > 0;
}
export default function(db) {
export default function() {
const router = express.Router();
// Set DEVICE_UUID_ENABLED to false to completely disable device UUID checking
@@ -96,109 +98,108 @@ export default function(db) {
router.use(authenticateJWT);
router.post('/clock', async (req, res) => {
// NEW: borrow a connection so we can set session time_zone
const conn = await db.getConnection();
try {
const { userId, eventType, qrCodeValue, latitude, longitude } = req.body;
// NEW: borrow a connection so we can set session time_zone
try {
const { userId, eventType, qrCodeValue, latitude, longitude } = req.body;
// 1) Kill Switch — now evaluated in the session's local day
const clockingAllowed = await isClockingEnabled(conn); // CHANGED: pass conn
if (!clockingAllowed) {
const note = 'Clock-in/out function is not enabled for today.';
await conn.execute( // CHANGED: use conn
`INSERT INTO clock_records
// 1) Kill Switch — now evaluated in the session's local day
const clockingAllowed = await isClockingEnabled(db); // CHANGED: pass conn
if (!clockingAllowed) {
const note = 'Clock-in/out function is not enabled for today.';
await db.execute( // CHANGED: use conn
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
[userId, qrCodeValue, latitude, longitude, note]
);
return res.status(403).json({ message: 'error.clockingDisabled' });
}
// 2) Geofence Validation (unchanged logic, just switch db -> conn)
if (latitude != null && longitude != null) {
const [activeFences] = await conn.execute('SELECT coordinates FROM geofences WHERE is_active = 1'); // CHANGED
if (activeFences.length === 0) {
const note = 'Cannot clock in: No active work area is defined.';
await conn.execute( // CHANGED
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
[userId, qrCodeValue, latitude, longitude, note]
);
return res.status(403).json({ message: 'error.noActiveGeofence' });
return res.status(403).json({ message: 'error.clockingDisabled' });
}
const userLocation = point([longitude, latitude]);
const parsedPolygons = [];
let isInside = false;
// 2) Geofence Validation (unchanged logic, just switch db -> conn)
if (latitude != null && longitude != null) {
const [activeFences] = await db.execute('SELECT coordinates FROM geofences WHERE is_active = 1'); // CHANGED
for (const fence of activeFences) {
try {
if (!fence.coordinates) continue;
const coordinates = JSON.parse(fence.coordinates);
const fencePolygon = polygon([coordinates]);
parsedPolygons.push(fencePolygon);
if (booleanPointInPolygon(userLocation, fencePolygon)) {
isInside = true;
break;
if (activeFences.length === 0) {
const note = 'Cannot clock in: No active work area is defined.';
await db.execute( // CHANGED
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
[userId, qrCodeValue, latitude, longitude, note]
);
return res.status(403).json({ message: 'error.noActiveGeofence' });
}
const userLocation = point([longitude, latitude]);
const parsedPolygons = [];
let isInside = false;
for (const fence of activeFences) {
try {
if (!fence.coordinates) continue;
const coordinates = JSON.parse(fence.coordinates);
const fencePolygon = polygon([coordinates]);
parsedPolygons.push(fencePolygon);
if (booleanPointInPolygon(userLocation, fencePolygon)) {
isInside = true;
break;
}
} catch (e) {
console.error('Could not parse geofence coordinates:', { coordinates: fence.coordinates, error: e });
}
} catch (e) {
console.error('Could not parse geofence coordinates:', { coordinates: fence.coordinates, error: e });
}
}
if (!isInside) {
let minDistance = Infinity;
for (const p of parsedPolygons) {
const distance = pointToLineDistance(userLocation, p.geometry.coordinates[0], { units: 'meters' });
if (distance < minDistance) minDistance = distance;
}
const distanceString = minDistance.toFixed(2);
const note = `Outside geofence by ${distanceString}m`;
await conn.execute( // CHANGED
`INSERT INTO clock_records
if (!isInside) {
let minDistance = Infinity;
for (const p of parsedPolygons) {
const distance = pointToLineDistance(userLocation, p.geometry.coordinates[0], { units: 'meters' });
if (distance < minDistance) minDistance = distance;
}
const distanceString = minDistance.toFixed(2);
const note = `Outside geofence by ${distanceString}m`;
await db.execute( // CHANGED
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, notes, timestamp)
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
[userId, qrCodeValue, latitude, longitude, note]
);
return res.status(403).json({ message: `error.outsideGeofence|${distanceString}` });
VALUES (?, "failed", ?, ?, ?, ?, CURRENT_TIME())`,
[userId, qrCodeValue, latitude, longitude, note]
);
return res.status(403).json({ message: `error.outsideGeofence|${distanceString}` });
}
}
}
// 3) QR Code and Status Validation (switch db -> conn; logic unchanged)
if (qrCodeValue !== 'FORCE_CLOCK_OUT') {
const [qrRows] = await conn.execute('SELECT is_active FROM qr_codes WHERE id = ?', [qrCodeValue]); // CHANGED
if (qrRows.length === 0 || !qrRows[0].is_active) {
return res.status(400).json({ message: 'error.invalidQrCode' });
// 3) QR Code and Status Validation (switch db -> conn; logic unchanged)
if (qrCodeValue !== 'FORCE_CLOCK_OUT') {
const [qrRows] = await db.execute('SELECT is_active FROM qr_codes WHERE id = ?', [qrCodeValue]); // CHANGED
if (qrRows.length === 0 || !qrRows[0].is_active) {
return res.status(400).json({ message: 'error.invalidQrCode' });
}
}
const [lastEvent] = await db.execute( // CHANGED
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
[userId]
);
if (lastEvent.length > 0 && lastEvent[0].event_type === eventType) {
const errorKey = eventType === 'clock_in' ? 'error.alreadyClockedIn' : 'error.alreadyClockedOut';
return res.status(400).json({ message: errorKey });
}
// 4) Record Successful Event — store UTC via SQL conversion (no JS date math)
await db.execute(
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, timestamp)
VALUES (?, ?, ?, ?, ?, CURRENT_TIME())`,
[userId, eventType, qrCodeValue, latitude, longitude]
);
res.status(201).json({ message: 'Clock event recorded.' });
} catch (error) {
console.error('!!! CRITICAL ERROR in /clock route !!!:', error);
res.status(500).json({ message: 'error.criticalServer' });
} finally {
;
}
const [lastEvent] = await conn.execute( // CHANGED
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
[userId]
);
if (lastEvent.length > 0 && lastEvent[0].event_type === eventType) {
const errorKey = eventType === 'clock_in' ? 'error.alreadyClockedIn' : 'error.alreadyClockedOut';
return res.status(400).json({ message: errorKey });
}
// 4) Record Successful Event — store UTC via SQL conversion (no JS date math)
await conn.execute(
`INSERT INTO clock_records
(worker_id, event_type, qr_code_id, latitude, longitude, timestamp)
VALUES (?, ?, ?, ?, ?, CURRENT_TIME())`,
[userId, eventType, qrCodeValue, latitude, longitude]
);
res.status(201).json({ message: 'Clock event recorded.' });
} catch (error) {
console.error('!!! CRITICAL ERROR in /clock route !!!:', error);
res.status(500).json({ message: 'error.criticalServer' });
} finally {
if (conn) conn.release();
}
});
});
router.get('/workers/:id', async (req, res) => {
const { id } = req.params;
@@ -220,7 +221,7 @@ export default function(db) {
const [rows] = await db.execute(`
SELECT cr.id, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName
FROM clock_records cr
LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id
LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id
WHERE cr.worker_id = ? ORDER BY cr.timestamp DESC
`, [userId]);
res.json(rows);