13 KiB
Database Schema Documentation - Nilai Clock Client
Overview
The Nilai Clock Client uses a MySQL 5.x database with MyISAM engine. The schema is designed for simplicity and performance, with consolidated device tracking and streamlined location updates.
Database Configuration
Connection Settings
// Backend database configuration
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
// MySQL 5.x specific settings
charset: 'utf8',
timezone: 'local'
}
Engine and Charset
- Engine: MyISAM (MySQL 5.x compatibility)
- Charset: UTF-8
- Collation: utf8_general_ci
Core Tables
workers
Primary user accounts table for both workers and managers.
CREATE TABLE `workers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password_hash` varchar(255) NOT NULL COMMENT 'Store hashed passwords, not plain text!',
`full_name` varchar(255) NOT NULL,
`role` enum('worker','manager') NOT NULL,
`device_uuid` varchar(255) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
KEY `idx_device_uuid` (`device_uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores user account information for both workers and managers';
Key Features:
- Consolidated device tracking via
device_uuidfield - Role-based access (worker/manager)
- Unique username constraint
- Indexed device UUID for fast lookups
Sample Data:
INSERT INTO workers (username, password_hash, full_name, role, device_uuid) VALUES
('worker001', '$2b$10$hash...', 'John Doe', 'worker', 'android-uuid-123'),
('manager001', '$2b$10$hash...', 'Jane Smith', 'manager', NULL);
clock_records
Time tracking events with location data.
CREATE TABLE `clock_records` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`worker_id` int(11) NOT NULL,
`event_type` enum('clock_in','clock_out','failed') NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`qr_code_id` varchar(255) DEFAULT NULL,
`latitude` decimal(10,8) DEFAULT NULL,
`longitude` decimal(11,8) DEFAULT NULL,
`notes` text,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_worker_id` (`worker_id`),
KEY `idx_event_type` (`event_type`),
KEY `idx_timestamp` (`timestamp`),
KEY `idx_qr_code_id` (`qr_code_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Records of worker clock in/out events';
Key Features:
- Links to workers table via
worker_id - Event types: clock_in, clock_out, failed
- Optional QR code reference
- Precise location coordinates (8 decimal places)
- Optional notes field for manual entries
Sample Data:
INSERT INTO clock_records (worker_id, event_type, qr_code_id, latitude, longitude) VALUES
(1, 'clock_in', 'qr-uuid-123', -6.20880000, 106.84560000),
(1, 'clock_out', 'qr-uuid-123', -6.20880000, 106.84560000);
qr_codes
QR code definitions and status.
CREATE TABLE `qr_codes` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_is_active` (`is_active`),
KEY `idx_name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='QR codes for clock in/out locations';
Key Features:
- UUID-based primary key
- Human-readable name
- Active/inactive status
- Creation timestamp
Sample Data:
INSERT INTO qr_codes (id, name, is_active) VALUES
('550e8400-e29b-41d4-a716-446655440000', 'Main Entrance', 1),
('550e8400-e29b-41d4-a716-446655440001', 'Side Gate', 1),
('550e8400-e29b-41d4-a716-446655440002', 'Warehouse Door', 0);
location_updates
Simplified location tracking for background monitoring.
CREATE TABLE `location_updates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`longitude` decimal(11,8) NOT NULL,
`latitude` decimal(10,8) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_created_at` (`created_at`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Background location updates for tracking';
Key Features:
- Simplified schema with only essential fields
- Longitude before latitude (geographic convention)
- User ID reference to workers table
- Timestamp for tracking history
Sample Data:
INSERT INTO location_updates (user_id, longitude, latitude) VALUES
(1, 106.84560000, -6.20880000),
(1, 106.84561000, -6.20881000);
Security Tables
security_alerts
Security violation logging.
CREATE TABLE `security_alerts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`alert_type` varchar(100) NOT NULL,
`alert_data` json DEFAULT NULL,
`severity` enum('low','medium','high','critical') DEFAULT 'medium',
`is_resolved` tinyint(1) DEFAULT '0',
`resolved_at` timestamp NULL DEFAULT NULL,
`resolved_by` int(11) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `resolved_by` (`resolved_by`),
KEY `idx_user_id` (`user_id`),
KEY `idx_alert_type` (`alert_type`),
KEY `idx_severity` (`severity`),
KEY `idx_is_resolved` (`is_resolved`),
KEY `idx_created_at` (`created_at`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Security alerts and violations';
Key Features:
- JSON data field for flexible alert information
- Severity levels: low, medium, high, critical
- Resolution tracking with timestamp and resolver
- Multiple indexes for efficient querying
Sample Data:
INSERT INTO security_alerts (user_id, alert_type, alert_data, severity) VALUES
(1, 'gps_spoofing_detected', '{"apps": ["com.fakegps"], "location": {"lat": -6.2088, "lng": 106.8456}}', 'high'),
(1, 'location_anomaly', '{"distance": 500, "time": 60}', 'medium');
security_checks
Periodic security assessments.
CREATE TABLE `security_checks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`device_info` json DEFAULT NULL,
`security_data` json DEFAULT NULL,
`risk_level` enum('low','medium','high') DEFAULT 'low',
`risk_score` int(11) DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_risk_level` (`risk_level`),
KEY `idx_timestamp` (`timestamp`),
KEY `idx_created_at` (`created_at`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Periodic security check results';
Key Features:
- JSON fields for flexible device and security data
- Risk level and numeric score
- Timestamp for tracking check frequency
Sample Data:
INSERT INTO security_checks (user_id, device_info, security_data, risk_level, risk_score) VALUES
(1, '{"platform": "android", "model": "Pixel 6"}', '{"apps": [], "rooted": false}', 'low', 10);
Management Tables
tags
Worker categorization tags.
CREATE TABLE `tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_name` varchar(100) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `tag_name` (`tag_name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Tags for categorizing workers';
worker_tags
Many-to-many relationship between workers and tags.
CREATE TABLE `worker_tags` (
`worker_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`worker_id`,`tag_id`),
KEY `idx_worker_id` (`worker_id`),
KEY `idx_tag_id` (`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Worker tag assignments';
Sample Data:
INSERT INTO tags (tag_name) VALUES ('Production'), ('Day Shift'), ('Night Shift');
INSERT INTO worker_tags (worker_id, tag_id) VALUES (1, 1), (1, 2);
Database Relationships
Entity Relationship Diagram
workers (1) ----< (M) clock_records
workers (1) ----< (M) location_updates
workers (1) ----< (M) security_alerts
workers (1) ----< (M) security_checks
workers (M) ----< (M) worker_tags >---- (M) tags
qr_codes (1) ----< (M) clock_records
Foreign Key Relationships (Logical)
Note: MyISAM engine doesn't support foreign key constraints, but logical relationships exist:
clock_records.worker_id→workers.idclock_records.qr_code_id→qr_codes.idlocation_updates.user_id→workers.idsecurity_alerts.user_id→workers.idsecurity_checks.user_id→workers.idworker_tags.worker_id→workers.idworker_tags.tag_id→tags.id
Indexing Strategy
Primary Indexes
- All tables have auto-increment primary keys
- Unique constraints on usernames and tag names
Performance Indexes
-- Workers table
KEY `idx_device_uuid` (`device_uuid`)
-- Clock records table
KEY `idx_worker_id` (`worker_id`)
KEY `idx_event_type` (`event_type`)
KEY `idx_timestamp` (`timestamp`)
KEY `idx_qr_code_id` (`qr_code_id`)
-- Location updates table
KEY `idx_user_id` (`user_id`)
KEY `idx_created_at` (`created_at`)
-- Security tables
KEY `idx_user_id` (`user_id`)
KEY `idx_alert_type` (`alert_type`)
KEY `idx_severity` (`severity`)
KEY `idx_risk_level` (`risk_level`)
Common Queries
Worker Authentication
-- Login verification
SELECT id, role, password_hash
FROM workers
WHERE username = ?;
-- Device validation
SELECT id, device_uuid
FROM workers
WHERE id = ? AND device_uuid = ?;
Clock Operations
-- Record clock event
INSERT INTO clock_records (worker_id, event_type, qr_code_id, latitude, longitude)
VALUES (?, ?, ?, ?, ?);
-- Get worker history
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
WHERE cr.worker_id = ?
ORDER BY cr.timestamp DESC;
Location Tracking
-- Insert location update
INSERT INTO location_updates (user_id, longitude, latitude)
VALUES (?, ?, ?);
-- Get recent locations
SELECT longitude, latitude, created_at
FROM location_updates
WHERE user_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY created_at DESC;
Security Monitoring
-- Log security alert
INSERT INTO security_alerts (user_id, alert_type, alert_data, severity)
VALUES (?, ?, ?, ?);
-- Get security status
SELECT * FROM security_checks
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 1;
Data Retention Policies
Location Data
- Keep location_updates for 30 days
- Archive older data to separate table
- Clean up automatically via scheduled job
Security Data
- Keep security_alerts indefinitely for audit
- Keep security_checks for 90 days
- Compress old data for storage efficiency
Clock Records
- Keep all clock_records permanently
- Essential for payroll and compliance
- Regular backup and archival
Backup Strategy
Daily Backups
# Full database backup
mysqldump -u username -p database_name > backup_$(date +%Y%m%d).sql
# Table-specific backups
mysqldump -u username -p database_name workers clock_records > critical_$(date +%Y%m%d).sql
Incremental Backups
# Binary log backup for point-in-time recovery
mysqlbinlog --start-datetime="2024-01-01 00:00:00" mysql-bin.000001 > incremental.sql
Performance Optimization
Query Optimization
- Use EXPLAIN to analyze query performance
- Add indexes for frequently queried columns
- Limit result sets with appropriate WHERE clauses
Table Optimization
-- Optimize tables periodically
OPTIMIZE TABLE workers, clock_records, location_updates;
-- Analyze tables for better query planning
ANALYZE TABLE workers, clock_records, location_updates;
Partitioning (Future Enhancement)
-- Partition location_updates by date for better performance
ALTER TABLE location_updates
PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p_2024_01 VALUES LESS THAN (TO_DAYS('2024-02-01')),
PARTITION p_2024_02 VALUES LESS THAN (TO_DAYS('2024-03-01')),
-- Add more partitions as needed
);
Migration Scripts
Version Updates
-- Example migration script for adding new columns
ALTER TABLE workers ADD COLUMN last_login timestamp NULL DEFAULT NULL;
ALTER TABLE workers ADD KEY idx_last_login (last_login);
-- Update existing data
UPDATE workers SET last_login = created_at WHERE last_login IS NULL;
Data Cleanup
-- Clean old location data (older than 30 days)
DELETE FROM location_updates
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
-- Archive old security checks
INSERT INTO security_checks_archive
SELECT * FROM security_checks
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
DELETE FROM security_checks
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);