Initial commit apps directory with .gitignore

This commit is contained in:
2026-02-22 15:15:41 +08:00
commit 0aa8cdd72c
228 changed files with 69672 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all achievement criteria
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, level, achievement_type, participant_type, group_size, score
FROM achievement_criteria
ORDER BY level, achievement_type, participant_type
`);
res.json(rows);
} catch (error) {
console.error('Error fetching achievement criteria:', error);
res.status(500).json({ error: 'Failed to fetch achievement criteria' });
}
});
// POST create new achievement criteria
router.post('/', async (req, res) => {
try {
const { level, achievementType, participantType, groupSize, score } = req.body;
// Check if combination already exists
const [existing] = await pool.execute(`
SELECT id FROM achievement_criteria
WHERE level = ? AND achievement_type = ? AND participant_type = ? AND group_size = ?
`, [level, achievementType, participantType, groupSize || null]);
if (existing.length > 0) {
return res.status(400).json({
status: 'error',
error: 'Kombinasi kriteria ini sudah ada'
});
}
const [result] = await pool.execute(`
INSERT INTO achievement_criteria (level, achievement_type, participant_type, group_size, score)
VALUES (?, ?, ?, ?, ?)
`, [level, achievementType, participantType, groupSize || null, score]);
res.json({
status: 'success',
id: result.insertId,
message: 'Kriteria prestasi berhasil ditambahkan'
});
} catch (error) {
console.error('Error creating achievement criteria:', error);
res.status(500).json({ status: 'error', error: 'Failed to create achievement criteria' });
}
});
// PUT update achievement criteria
router.put('/:id', async (req, res) => {
try {
const { level, achievementType, participantType, groupSize, score } = req.body;
const { id } = req.params;
// Check if combination already exists for other entries
const [existing] = await pool.execute(`
SELECT id FROM achievement_criteria
WHERE level = ? AND achievement_type = ? AND participant_type = ? AND group_size = ? AND id != ?
`, [level, achievementType, participantType, groupSize || null, id]);
if (existing.length > 0) {
return res.status(400).json({
status: 'error',
error: 'Kombinasi kriteria ini sudah ada'
});
}
await pool.execute(`
UPDATE achievement_criteria
SET level = ?, achievement_type = ?, participant_type = ?, group_size = ?, score = ?
WHERE id = ?
`, [level, achievementType, participantType, groupSize || null, score, id]);
res.json({ status: 'success', message: 'Kriteria prestasi berhasil diperbarui' });
} catch (error) {
console.error('Error updating achievement criteria:', error);
res.status(500).json({ status: 'error', error: 'Failed to update achievement criteria' });
}
});
// DELETE achievement criteria
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM achievement_criteria WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Kriteria prestasi berhasil dihapus' });
} catch (error) {
console.error('Error deleting achievement criteria:', error);
res.status(500).json({ status: 'error', error: 'Failed to delete achievement criteria' });
}
});
// POST import bulk achievement criteria
router.post('/import', async (req, res) => {
try {
const { criteria } = req.body;
let success = 0;
let failed = 0;
const errors = [];
for (const item of criteria) {
try {
const { level, achievementType, participantType, groupSize, score } = item;
// Insert or update if combination exists
await pool.execute(`
INSERT INTO achievement_criteria (level, achievement_type, participant_type, group_size, score)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE score = VALUES(score)
`, [level, achievementType, participantType, groupSize || null, score]);
success++;
} catch (e) {
failed++;
errors.push(`${item.level} - ${item.achievementType}: ${e.message}`);
}
}
res.json({ status: 'success', success, failed, errors });
} catch (error) {
console.error('Error importing achievement criteria:', error);
res.status(500).json({ status: 'error', error: 'Failed to import achievement criteria' });
}
});
// GET score by criteria (for use in achievement form)
router.get('/score', async (req, res) => {
try {
const { level, achievementType, participantType, groupSize } = req.query;
const [rows] = await pool.execute(`
SELECT score FROM achievement_criteria
WHERE level = ? AND achievement_type = ? AND participant_type = ? AND (group_size = ? OR group_size IS NULL)
ORDER BY group_size DESC
LIMIT 1
`, [level, achievementType, participantType, groupSize || null]);
if (rows.length > 0) {
res.json({ score: rows[0].score });
} else {
res.json({ score: null, message: 'Kriteria tidak ditemukan' });
}
} catch (error) {
console.error('Error fetching score:', error);
res.status(500).json({ error: 'Failed to fetch score' });
}
});
export default router;

View File

@@ -0,0 +1,65 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all achievements
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, student_id as studentId, title, level, date, score_reduction as scoreReduction
FROM achievements
ORDER BY date DESC
`);
res.json(rows);
} catch (error) {
console.error('Error fetching achievements:', error);
res.status(500).json({ error: 'Failed to fetch achievements' });
}
});
// GET achievements by student
router.get('/student/:studentId', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, student_id as studentId, title, level, date, score_reduction as scoreReduction
FROM achievements
WHERE student_id = ?
ORDER BY date DESC
`, [req.params.studentId]);
res.json(rows);
} catch (error) {
console.error('Error fetching student achievements:', error);
res.status(500).json({ error: 'Failed to fetch achievements' });
}
});
// POST create achievement
router.post('/', async (req, res) => {
try {
const { id, studentId, title, level, date, scoreReduction } = req.body;
await pool.execute(`
INSERT INTO achievements (id, student_id, title, level, date, score_reduction)
VALUES (?, ?, ?, ?, ?, ?)
`, [id || Date.now().toString(), studentId, title, level, date || new Date().toISOString().split('T')[0], scoreReduction]);
res.status(201).json({ status: 'success', message: 'Achievement recorded' });
} catch (error) {
console.error('Error creating achievement:', error);
res.status(500).json({ error: 'Failed to create achievement' });
}
});
// DELETE achievement
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM achievements WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Achievement deleted' });
} catch (error) {
console.error('Error deleting achievement:', error);
res.status(500).json({ error: 'Failed to delete achievement' });
}
});
export default router;

View File

@@ -0,0 +1,80 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all settings
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute('SELECT setting_key, setting_value FROM settings');
// Convert array to object
const settings = {};
rows.forEach(row => {
settings[row.setting_key] = row.setting_value;
});
res.json(settings);
} catch (error) {
console.error('Error fetching settings:', error);
res.status(500).json({ error: 'Failed to fetch settings' });
}
});
// GET single setting
router.get('/:key', async (req, res) => {
try {
const [rows] = await pool.execute(
'SELECT setting_value FROM settings WHERE setting_key = ?',
[req.params.key]
);
if (rows.length === 0) {
return res.status(404).json({ error: 'Setting not found' });
}
res.json({ key: req.params.key, value: rows[0].setting_value });
} catch (error) {
console.error('Error fetching setting:', error);
res.status(500).json({ error: 'Failed to fetch setting' });
}
});
// PUT update settings (bulk)
router.put('/', async (req, res) => {
try {
const settings = req.body;
for (const [key, value] of Object.entries(settings)) {
await pool.execute(`
INSERT INTO settings (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = ?
`, [key, value, value]);
}
res.json({ status: 'success', message: 'Settings updated' });
} catch (error) {
console.error('Error updating settings:', error);
res.status(500).json({ error: 'Failed to update settings' });
}
});
// PUT update single setting
router.put('/:key', async (req, res) => {
try {
const { value } = req.body;
await pool.execute(`
INSERT INTO settings (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = ?
`, [req.params.key, value, value]);
res.json({ status: 'success', message: 'Setting updated' });
} catch (error) {
console.error('Error updating setting:', error);
res.status(500).json({ error: 'Failed to update setting' });
}
});
export default router;

View File

@@ -0,0 +1,312 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all students
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, name, class, parent_name as parentName, parent_phone as parentPhone, shift, face_descriptor as faceDescriptor
FROM students
ORDER BY class, name
`);
res.json(rows);
} catch (error) {
console.error('Error fetching students:', error);
res.status(500).json({ error: 'Failed to fetch students' });
}
});
// GET single student
router.get('/:id', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, name, class, parent_name as parentName, parent_phone as parentPhone, shift, face_descriptor as faceDescriptor
FROM students
WHERE id = ?
`, [req.params.id]);
if (rows.length === 0) {
return res.status(404).json({ error: 'Student not found' });
}
res.json(rows[0]);
} catch (error) {
console.error('Error fetching student:', error);
res.status(500).json({ error: 'Failed to fetch student' });
}
});
// POST create student
router.post('/', async (req, res) => {
try {
const { id, name, class: studentClass, parentName, parentPhone, shift } = req.body;
await pool.execute(`
INSERT INTO students (id, name, class, parent_name, parent_phone, shift)
VALUES (?, ?, ?, ?, ?, ?)
`, [id, name, studentClass, parentName, parentPhone, shift || 'Siang']);
res.status(201).json({ status: 'success', message: 'Student created' });
} catch (error) {
console.error('Error creating student:', error);
res.status(500).json({ error: 'Failed to create student' });
}
});
// PUT update student
router.put('/:id', async (req, res) => {
try {
const { name, class: studentClass, parentName, parentPhone, shift, faceDescriptor } = req.body;
await pool.execute(`
UPDATE students
SET name = ?, class = ?, parent_name = ?, parent_phone = ?, shift = ?, face_descriptor = ?
WHERE id = ?
`, [name, studentClass, parentName, parentPhone, shift, faceDescriptor, req.params.id]);
res.json({ status: 'success', message: 'Student updated' });
} catch (error) {
console.error('Error updating student:', error);
res.status(500).json({ error: 'Failed to update student' });
}
});
// DELETE student
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM students WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Student deleted' });
} catch (error) {
console.error('Error deleting student:', error);
res.status(500).json({ error: 'Failed to delete student' });
}
});
// GET class statistics for promotion
router.get('/stats/classes', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT class, COUNT(*) as count
FROM students
GROUP BY class
ORDER BY class
`);
res.json(rows);
} catch (error) {
console.error('Error fetching class stats:', error);
res.status(500).json({ error: 'Failed to fetch class stats' });
}
});
// POST promote students to next class
router.post('/promote', async (req, res) => {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// Get promotion mapping: X -> XI, XI -> XII, XII -> LULUS (delete)
const promotionRules = [
{ from: 'X', to: 'XI' },
{ from: 'XI', to: 'XII' },
];
let promoted = 0;
let graduated = 0;
// First, handle graduates (XII -> Lulus/Delete)
// Count XII students first
const [xiiStudents] = await connection.execute(
"SELECT COUNT(*) as count FROM students WHERE class LIKE 'XII%'"
);
graduated = xiiStudents[0].count;
// Archive XII students before deletion (optional - can create alumni table later)
// For now, just delete them
await connection.execute("DELETE FROM students WHERE class LIKE 'XII%'");
// Promote XI to XII
const [xiResult] = await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'XI-', 'XII-')
WHERE class LIKE 'XI-%'
`);
// Also handle XI without dash
await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'XI', 'XII')
WHERE class = 'XI' OR (class LIKE 'XI%' AND class NOT LIKE 'XII%')
`);
// Promote X to XI
const [xResult] = await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'X-', 'XI-')
WHERE class LIKE 'X-%' AND class NOT LIKE 'XI%' AND class NOT LIKE 'XII%'
`);
// Also handle X without dash
await connection.execute(`
UPDATE students
SET class = 'XI'
WHERE class = 'X'
`);
// Count promoted students
const [countResult] = await connection.execute('SELECT COUNT(*) as count FROM students');
promoted = countResult[0].count;
await connection.commit();
res.json({
status: 'success',
message: 'Kenaikan kelas berhasil!',
graduated,
totalRemaining: promoted
});
} catch (error) {
await connection.rollback();
console.error('Error promoting students:', error);
res.status(500).json({ error: 'Failed to promote students' });
} finally {
connection.release();
}
});
// POST graduate specific class (manual)
router.post('/graduate/:classPrefix', async (req, res) => {
try {
const { classPrefix } = req.params;
const [result] = await pool.execute(
'DELETE FROM students WHERE class LIKE ?',
[`${classPrefix}%`]
);
res.json({
status: 'success',
message: `Siswa kelas ${classPrefix} telah diluluskan`,
count: result.affectedRows
});
} catch (error) {
console.error('Error graduating students:', error);
res.status(500).json({ error: 'Failed to graduate students' });
}
});
// POST promote individual students with mapping
router.post('/promote-mapping', async (req, res) => {
const { mappings } = req.body; // Array of { studentId, newClass }
if (!mappings || !Array.isArray(mappings) || mappings.length === 0) {
return res.status(400).json({ error: 'Data mapping diperlukan' });
}
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
let success = 0;
let failed = 0;
const errors = [];
const graduatedIds = [];
for (const mapping of mappings) {
const { studentId, newClass } = mapping;
if (!studentId) {
failed++;
errors.push(`ID siswa tidak valid`);
continue;
}
try {
if (newClass === 'LULUS' || newClass === 'lulus' || newClass === '') {
// Graduate this student (delete)
await connection.execute(
'DELETE FROM students WHERE id = ?',
[studentId]
);
graduatedIds.push(studentId);
success++;
} else {
// Update class
const [result] = await connection.execute(
'UPDATE students SET class = ? WHERE id = ?',
[newClass, studentId]
);
if (result.affectedRows > 0) {
success++;
} else {
failed++;
errors.push(`Siswa ${studentId} tidak ditemukan`);
}
}
} catch (err) {
failed++;
errors.push(`Error untuk siswa ${studentId}: ${err.message}`);
}
}
await connection.commit();
res.json({
status: 'success',
message: `Proses selesai: ${success} berhasil, ${failed} gagal`,
success,
failed,
graduated: graduatedIds.length,
errors: errors.slice(0, 10) // Limit error messages
});
} catch (error) {
await connection.rollback();
console.error('Error in promote-mapping:', error);
res.status(500).json({ error: 'Gagal memproses kenaikan kelas' });
} finally {
connection.release();
}
});
// GET available classes for dropdown
router.get('/available-classes', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT DISTINCT class
FROM students
ORDER BY class
`);
// Generate next level classes
const currentClasses = rows.map(r => r.class);
const allClasses = new Set(currentClasses);
// Add common class patterns for next level
currentClasses.forEach(c => {
// Extract level and suffix (e.g., "X-A" -> "X", "A")
const match = c.match(/^(X|XI|XII)-?(.*)$/i);
if (match) {
const level = match[1].toUpperCase();
const suffix = match[2];
if (level === 'X') {
allClasses.add(`XI-${suffix}`);
allClasses.add(`XI${suffix}`);
} else if (level === 'XI') {
allClasses.add(`XII-${suffix}`);
allClasses.add(`XII${suffix}`);
}
}
});
// Sort and return unique classes
const sortedClasses = Array.from(allClasses).sort();
res.json(sortedClasses);
} catch (error) {
console.error('Error fetching available classes:', error);
res.status(500).json({ error: 'Failed to fetch classes' });
}
});
export default router;

View File

@@ -0,0 +1,160 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all users (without passwords)
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, username, name, role, permissions, is_active as isActive, created_at as createdAt
FROM users
ORDER BY created_at DESC
`);
// Parse permissions JSON
const users = rows.map(user => ({
...user,
permissions: user.permissions ? JSON.parse(user.permissions) : []
}));
res.json(users);
} catch (error) {
console.error('Error fetching users:', error);
res.status(500).json({ error: 'Failed to fetch users' });
}
});
// GET single user
router.get('/:id', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, username, name, role, permissions, is_active as isActive
FROM users
WHERE id = ?
`, [req.params.id]);
if (rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
const user = {
...rows[0],
permissions: rows[0].permissions ? JSON.parse(rows[0].permissions) : []
};
res.json(user);
} catch (error) {
console.error('Error fetching user:', error);
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// POST create user
router.post('/', async (req, res) => {
try {
const { username, password, name, role, permissions } = req.body;
// Check if username already exists
const [existing] = await pool.execute('SELECT id FROM users WHERE username = ?', [username]);
if (existing.length > 0) {
return res.status(400).json({ error: 'Username sudah digunakan' });
}
const permissionsJson = JSON.stringify(permissions || []);
await pool.execute(`
INSERT INTO users (username, password, name, role, permissions)
VALUES (?, ?, ?, ?, ?)
`, [username, password, name, role || 'staff', permissionsJson]);
res.status(201).json({ status: 'success', message: 'User created' });
} catch (error) {
console.error('Error creating user:', error);
res.status(500).json({ error: 'Failed to create user' });
}
});
// PUT update user
router.put('/:id', async (req, res) => {
try {
const { username, password, name, role, permissions, isActive } = req.body;
const permissionsJson = JSON.stringify(permissions || []);
// Check if username already exists (for another user)
const [existing] = await pool.execute(
'SELECT id FROM users WHERE username = ? AND id != ?',
[username, req.params.id]
);
if (existing.length > 0) {
return res.status(400).json({ error: 'Username sudah digunakan oleh user lain' });
}
if (password && password.trim() !== '') {
await pool.execute(`
UPDATE users
SET username = ?, password = ?, name = ?, role = ?, permissions = ?, is_active = ?
WHERE id = ?
`, [username, password, name, role, permissionsJson, isActive !== false, req.params.id]);
} else {
await pool.execute(`
UPDATE users
SET username = ?, name = ?, role = ?, permissions = ?, is_active = ?
WHERE id = ?
`, [username, name, role, permissionsJson, isActive !== false, req.params.id]);
}
res.json({ status: 'success', message: 'User updated' });
} catch (error) {
console.error('Error updating user:', error);
res.status(500).json({ error: 'Failed to update user' });
}
});
// DELETE user
router.delete('/:id', async (req, res) => {
try {
// Prevent deleting the last admin
const [admins] = await pool.execute("SELECT id FROM users WHERE role = 'admin' AND is_active = TRUE");
const [userToDelete] = await pool.execute('SELECT role FROM users WHERE id = ?', [req.params.id]);
if (userToDelete.length > 0 && userToDelete[0].role === 'admin' && admins.length <= 1) {
return res.status(400).json({ error: 'Tidak dapat menghapus admin terakhir' });
}
await pool.execute('DELETE FROM users WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'User deleted' });
} catch (error) {
console.error('Error deleting user:', error);
res.status(500).json({ error: 'Failed to delete user' });
}
});
// POST login - verify credentials and return user with permissions
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const [rows] = await pool.execute(`
SELECT id, username, name, role, permissions, is_active as isActive
FROM users
WHERE username = ? AND password = ? AND is_active = TRUE
`, [username, password]);
if (rows.length === 0) {
return res.status(401).json({ error: 'Username atau password salah' });
}
const user = {
...rows[0],
permissions: rows[0].permissions ? JSON.parse(rows[0].permissions) : []
};
res.json({ status: 'success', user });
} catch (error) {
console.error('Error during login:', error);
res.status(500).json({ error: 'Login failed' });
}
});
export default router;

View File

@@ -0,0 +1,132 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all violation rules
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, code, description, category, score, default_sanction
FROM violation_rules
ORDER BY category, score DESC
`);
res.json(rows);
} catch (error) {
console.error('Error fetching violation rules:', error);
res.status(500).json({ error: 'Failed to fetch violation rules' });
}
});
// POST create new violation rule
router.post('/', async (req, res) => {
try {
const { code, description, category, score, defaultSanction } = req.body;
// Check if code already exists
const [existing] = await pool.execute(
'SELECT id FROM violation_rules WHERE code = ?', [code]
);
if (existing.length > 0) {
return res.status(400).json({
status: 'error',
error: `Kode "${code}" sudah digunakan`
});
}
const [result] = await pool.execute(`
INSERT INTO violation_rules (code, description, category, score, default_sanction)
VALUES (?, ?, ?, ?, ?)
`, [code, description, category, score, defaultSanction || null]);
res.json({
status: 'success',
id: result.insertId,
message: 'Jenis pelanggaran berhasil ditambahkan'
});
} catch (error) {
console.error('Error creating violation rule:', error);
res.status(500).json({ status: 'error', error: 'Failed to create violation rule' });
}
});
// PUT update violation rule
router.put('/:id', async (req, res) => {
try {
const { code, description, category, score, defaultSanction } = req.body;
const { id } = req.params;
// Check if code already exists for other rules
const [existing] = await pool.execute(
'SELECT id FROM violation_rules WHERE code = ? AND id != ?', [code, id]
);
if (existing.length > 0) {
return res.status(400).json({
status: 'error',
error: `Kode "${code}" sudah digunakan`
});
}
await pool.execute(`
UPDATE violation_rules
SET code = ?, description = ?, category = ?, score = ?, default_sanction = ?
WHERE id = ?
`, [code, description, category, score, defaultSanction || null, id]);
res.json({ status: 'success', message: 'Jenis pelanggaran berhasil diperbarui' });
} catch (error) {
console.error('Error updating violation rule:', error);
res.status(500).json({ status: 'error', error: 'Failed to update violation rule' });
}
});
// DELETE violation rule
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM violation_rules WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Jenis pelanggaran berhasil dihapus' });
} catch (error) {
console.error('Error deleting violation rule:', error);
res.status(500).json({ status: 'error', error: 'Failed to delete violation rule' });
}
});
// POST import bulk violation rules
router.post('/import', async (req, res) => {
try {
const { rules } = req.body;
let success = 0;
let failed = 0;
const errors = [];
for (const rule of rules) {
try {
const { code, description, category, score, defaultSanction } = rule;
// Insert or update if code exists
await pool.execute(`
INSERT INTO violation_rules (code, description, category, score, default_sanction)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
description = VALUES(description),
category = VALUES(category),
score = VALUES(score),
default_sanction = VALUES(default_sanction)
`, [code, description, category, score, defaultSanction || null]);
success++;
} catch (e) {
failed++;
errors.push(`${rule.code}: ${e.message}`);
}
}
res.json({ status: 'success', success, failed, errors });
} catch (error) {
console.error('Error importing violation rules:', error);
res.status(500).json({ status: 'error', error: 'Failed to import violation rules' });
}
});
export default router;

View File

@@ -0,0 +1,85 @@
import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all violations
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, student_id as studentId, rule_id as ruleId, date, description,
score, sanction, timestamp, photo_url as photoUrl
FROM violations
ORDER BY timestamp DESC
`);
res.json(rows);
} catch (error) {
console.error('Error fetching violations:', error);
res.status(500).json({ error: 'Failed to fetch violations' });
}
});
// GET violations by student
router.get('/student/:studentId', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, student_id as studentId, rule_id as ruleId, date, description,
score, sanction, timestamp, photo_url as photoUrl
FROM violations
WHERE student_id = ?
ORDER BY timestamp DESC
`, [req.params.studentId]);
res.json(rows);
} catch (error) {
console.error('Error fetching student violations:', error);
res.status(500).json({ error: 'Failed to fetch violations' });
}
});
// POST create violation
router.post('/', async (req, res) => {
try {
const { id, studentId, ruleId, date, description, score, sanction, timestamp, photoUrl } = req.body;
await pool.execute(`
INSERT INTO violations (id, student_id, rule_id, date, description, score, sanction, timestamp, photo_url)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [id || Date.now().toString(), studentId, ruleId, date, description, score, sanction || '-', timestamp || Date.now(), photoUrl || null]);
res.status(201).json({ status: 'success', message: 'Violation recorded' });
} catch (error) {
console.error('Error creating violation:', error);
res.status(500).json({ error: 'Failed to create violation' });
}
});
// PUT update violation
router.put('/:id', async (req, res) => {
try {
const { score, description, sanction } = req.body;
// We only allow updating score, description, sanction for now as per sync requirement
await pool.execute(`
UPDATE violations
SET score = ?, description = ?, sanction = ?
WHERE id = ?
`, [score, description, sanction, req.params.id]);
res.json({ status: 'success', message: 'Violation updated' });
} catch (error) {
console.error('Error updating violation:', error);
res.status(500).json({ error: 'Failed to update violation' });
}
});
// DELETE violation
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM violations WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Violation deleted' });
} catch (error) {
console.error('Error deleting violation:', error);
res.status(500).json({ error: 'Failed to delete violation' });
}
});
export default router;