Files
smanab/Sistem-Pelanggaran-Siswa/database.js

303 lines
13 KiB
JavaScript

import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
dotenv.config();
// Database connection pool
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '3306'),
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'sipasi_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// Auto-create tables on startup
export async function initDatabase() {
const connection = await pool.getConnection();
try {
console.log('🔌 Connected to MySQL database');
// Create students table
await connection.execute(`
CREATE TABLE IF NOT EXISTS students (
id VARCHAR(20) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
class VARCHAR(50) NOT NULL,
parent_name VARCHAR(255),
parent_phone VARCHAR(20),
shift VARCHAR(20) DEFAULT 'Siang',
face_descriptor TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`);
console.log('✅ Table "students" ready');
// Add columns if they don't exist (for existing databases)
try {
await connection.execute('ALTER TABLE students ADD COLUMN IF NOT EXISTS shift VARCHAR(20) DEFAULT "Siang"');
await connection.execute('ALTER TABLE students ADD COLUMN IF NOT EXISTS face_descriptor TEXT');
} catch (err) {
// Ignore if columns already exist or IF NOT EXISTS is not supported
}
// Create violations table
await connection.execute(`
CREATE TABLE IF NOT EXISTS violations (
id VARCHAR(50) PRIMARY KEY,
student_id VARCHAR(20) NOT NULL,
rule_id VARCHAR(10) NOT NULL,
date DATE NOT NULL,
description TEXT,
score INT NOT NULL DEFAULT 0,
sanction VARCHAR(255),
academic_year VARCHAR(20),
timestamp BIGINT,
photo_url TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
)
`);
console.log('✅ Table "violations" ready');
try {
await connection.execute('ALTER TABLE violations ADD COLUMN IF NOT EXISTS academic_year VARCHAR(20)');
} catch (err) { }
// Create achievements table
await connection.execute(`
CREATE TABLE IF NOT EXISTS achievements (
id VARCHAR(50) PRIMARY KEY,
student_id VARCHAR(20) NOT NULL,
title VARCHAR(255) NOT NULL,
level ENUM('Nasional', 'Provinsi', 'Kabupaten/Kota', 'Sekolah') NOT NULL,
date DATE NOT NULL,
score_reduction INT NOT NULL DEFAULT 0,
academic_year VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
)
`);
console.log('✅ Table "achievements" ready');
try {
await connection.execute('ALTER TABLE achievements ADD COLUMN IF NOT EXISTS academic_year VARCHAR(20)');
} catch (err) { }
// Create settings table
await connection.execute(`
CREATE TABLE IF NOT EXISTS settings (
setting_key VARCHAR(100) PRIMARY KEY,
setting_value LONGTEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`);
console.log('✅ Table "settings" ready');
// Migration: Update setting_value to LONGTEXT for existing tables
try {
await connection.execute('ALTER TABLE settings MODIFY COLUMN setting_value LONGTEXT');
} catch (err) { }
await connection.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role ENUM('admin', 'guru', 'staff') DEFAULT 'staff',
permissions TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`);
console.log('✅ Table "users" ready');
// Create violation_rules table for dynamic violation categories
await connection.execute(`
CREATE TABLE IF NOT EXISTS violation_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) NOT NULL UNIQUE,
description VARCHAR(500) NOT NULL,
category ENUM('Perilaku', 'Kerajinan', 'Kerapian') NOT NULL,
score INT NOT NULL DEFAULT 0,
default_sanction VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`);
console.log('✅ Table "violation_rules" ready');
// Create achievement_criteria table for achievement scoring reference
await connection.execute(`
CREATE TABLE IF NOT EXISTS achievement_criteria (
id INT AUTO_INCREMENT PRIMARY KEY,
level ENUM('Kabupaten/Kota', 'Provinsi', 'Nasional', 'Internasional') NOT NULL,
achievement_type ENUM('Juara I', 'Juara II', 'Juara III', 'Harapan I', 'Harapan II', 'Harapan III') NOT NULL,
participant_type ENUM('Perorangan', 'Kelompok') NOT NULL,
group_size ENUM('1-3 orang', '4-6 orang', '7-12 orang', 'Lebih dari 12 orang') DEFAULT NULL,
score INT NOT NULL DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_criteria (level, achievement_type, participant_type, group_size)
)
`);
console.log('✅ Table "achievement_criteria" ready');
// Insert default admin user if not exists
const [userRows] = await connection.execute('SELECT COUNT(*) as count FROM users');
if (userRows[0].count === 0) {
const defaultPermissions = JSON.stringify(['dashboard', 'violation', 'achievement', 'students', 'report', 'settings']);
await connection.execute(`
INSERT INTO users (username, password, name, role, permissions)
VALUES ('admin', 'Smanab100%', 'Administrator', 'admin', ?)
`, [defaultPermissions]);
console.log('✅ Default admin user created');
}
// Insert default violation rules if table is empty
const [violationRulesRows] = await connection.execute('SELECT COUNT(*) as count FROM violation_rules');
if (violationRulesRows[0].count === 0) {
const defaultRules = [
['A1', 'Melakukan atau terlibat tindakan pidana', 'Perilaku', 100, null],
['A2', 'Membawa dan menggunakan narkoba', 'Perilaku', 100, null],
['A3', 'Menganiaya/intimidasi pada guru, kepala sekolah, dll', 'Perilaku', 50, null],
['A4', 'Membawa dan mengkonsumsi miras', 'Perilaku', 25, null],
['A5', 'Membawa dan menggunakan senjata tajam tanpa ijin', 'Perilaku', 25, null],
['A6', 'Membawa dan mengedarkan tayangan porno (buku, VCD, dll)', 'Perilaku', 25, null],
['A7', 'Berkelahi atau terlibat tawuran', 'Perilaku', 25, null],
['A8', 'Berbuat asusila', 'Perilaku', 25, null],
['A9', 'Merusak sarana & prasarana milik sekolah dan orang lain', 'Perilaku', 25, 'Mengganti sarana'],
['A10', 'Membawa rokok atau merokok di sekolah', 'Perilaku', 15, null],
['A11', 'Mengancam warga sekolah dan orang lain', 'Perilaku', 15, null],
['A12', 'Memalsukan tanda tangan kepala sekolah, guru dan stempel', 'Perilaku', 15, null],
['A13', 'Berada di luar lingkungan sekolah tanpa ijin saat PBM', 'Perilaku', 15, null],
['A14', 'Menerobos atau melompat pagar sekolah', 'Perilaku', 15, null],
['B1', 'Absen tanpa keterangan (Alpha)', 'Kerajinan', 5, null],
['B2', 'Tidak menyelesaikan administrasi yang ditentukan', 'Kerajinan', 1, null],
['B3', 'Terlambat hadir ke sekolah', 'Kerajinan', 1, null],
['B4', 'Terlambat mengikuti pembelajaran', 'Kerajinan', 1, null],
['B5', 'Terlambat menyerahkan tugas', 'Kerajinan', 1, null],
['B6', 'Absen mengikuti ulangan tanpa ijin', 'Kerajinan', 2, null],
['B7', 'Absen mengikuti upacara sekolah tanpa ijin', 'Kerajinan', 2, null],
['B8', 'Kesalahan yang terulang sampai 3x', 'Kerajinan', 6, null],
['C1', 'Memakai pakaian seragam tidak sesuai dengan ketentuan', 'Kerapian', 5, null],
['C2', 'Memelihara rambut tidak sesuai dengan ketentuan', 'Kerapian', 5, null],
['C3', 'Mengecat rambut selain warna hitam', 'Kerapian', 5, null],
['C4', 'Siswa putra/putri memakai perhiasan berlebihan', 'Kerapian', 5, null]
];
for (const rule of defaultRules) {
await connection.execute(`
INSERT INTO violation_rules (code, description, category, score, default_sanction)
VALUES (?, ?, ?, ?, ?)
`, rule);
}
console.log('✅ Default violation rules inserted');
}
// Insert default achievement criteria if table is empty
const [achievementCriteriaRows] = await connection.execute('SELECT COUNT(*) as count FROM achievement_criteria');
if (achievementCriteriaRows[0].count === 0) {
const levels = ['Kabupaten/Kota', 'Provinsi', 'Nasional', 'Internasional'];
const achievements = ['Juara I', 'Juara II', 'Juara III', 'Harapan I', 'Harapan II', 'Harapan III'];
const groupSizes = ['1-3 orang', '4-6 orang', '7-12 orang', 'Lebih dari 12 orang'];
const baseScores = {
'Internasional': { 'Juara I': 50, 'Juara II': 45, 'Juara III': 40, 'Harapan I': 35, 'Harapan II': 30, 'Harapan III': 25 },
'Nasional': { 'Juara I': 40, 'Juara II': 35, 'Juara III': 30, 'Harapan I': 25, 'Harapan II': 20, 'Harapan III': 15 },
'Provinsi': { 'Juara I': 30, 'Juara II': 25, 'Juara III': 20, 'Harapan I': 15, 'Harapan II': 10, 'Harapan III': 5 },
'Kabupaten/Kota': { 'Juara I': 20, 'Juara II': 15, 'Juara III': 10, 'Harapan I': 8, 'Harapan II': 5, 'Harapan III': 3 }
};
// Insert Perorangan criteria
for (const level of levels) {
for (const achievement of achievements) {
const score = baseScores[level][achievement];
await connection.execute(`
INSERT INTO achievement_criteria (level, achievement_type, participant_type, group_size, score)
VALUES (?, ?, 'Perorangan', NULL, ?)
`, [level, achievement, score]);
}
}
// Insert Kelompok criteria with different group sizes
for (const level of levels) {
for (const achievement of achievements) {
for (const groupSize of groupSizes) {
// Score decreases slightly for larger groups
let baseScore = baseScores[level][achievement];
if (groupSize === '1-3 orang') baseScore = Math.floor(baseScore * 0.9);
else if (groupSize === '4-6 orang') baseScore = Math.floor(baseScore * 0.8);
else if (groupSize === '7-12 orang') baseScore = Math.floor(baseScore * 0.7);
else baseScore = Math.floor(baseScore * 0.6);
await connection.execute(`
INSERT INTO achievement_criteria (level, achievement_type, participant_type, group_size, score)
VALUES (?, ?, 'Kelompok', ?, ?)
`, [level, achievement, groupSize, baseScore]);
}
}
}
console.log('✅ Default achievement criteria inserted');
}
// Insert default settings if not exists
const defaultSettings = [
['schoolName', 'SMA Negeri 1 Abiansemal'],
['academicYear', '2025/2026'],
['logoBase64', ''],
['kopBase64', ''],
['principalName', ''],
['principalNip', ''],
['counselorName', ''],
['counselorNip', '']
];
for (const [key, value] of defaultSettings) {
await connection.execute(`
INSERT IGNORE INTO settings (setting_key, setting_value) VALUES (?, ?)
`, [key, value]);
}
console.log('✅ Default settings initialized');
// Insert sample students if table is empty
const [studentRows] = await connection.execute('SELECT COUNT(*) as count FROM students');
if (studentRows[0].count === 0) {
const sampleStudents = [
['2023001', 'I Putu Gede Mahendra', 'X-1', 'I Made Wijaya', '081234567890', 'Pagi'],
['2023002', 'Ni Kadek Ayu Lestari', 'X-1', 'I Ketut Arta', '081987654321', 'Pagi'],
['2023003', 'Komang Budi Utama', 'XI-IPA-2', 'Wayan Sudira', '08122334455', 'Siang'],
['2023004', 'Dewa Ayu Dewi', 'XII-IPS-1', 'Dewa Rai', '08177665544', 'Siang']
];
for (const student of sampleStudents) {
await connection.execute(`
INSERT INTO students (id, name, class, parent_name, parent_phone, shift) VALUES (?, ?, ?, ?, ?, ?)
`, student);
}
console.log('✅ Sample students inserted');
}
console.log('🎉 Database initialization complete!');
} catch (error) {
console.error('❌ Database initialization error:', error);
throw error;
} finally {
connection.release();
}
}
export default pool;