import { NextRequest, NextResponse } from 'next/server'; import supabase from '@/lib/db'; import * as XLSX from 'xlsx'; interface MataKuliahUpload { kode_mk: string; nama_mk: string; sks: number; semester: number; jenis_mk: 'Wajib' | 'Pilihan Wajib' | 'Pilihan'; kode_prasyarat?: string; } export async function POST(request: NextRequest) { try { const formData = await request.formData(); const file = formData.get('file') as File; if (!file) { return NextResponse.json( { message: 'No file uploaded' }, { status: 400 } ); } // Validate file type const validTypes = [ 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv' ]; if (!validTypes.includes(file.type)) { return NextResponse.json( { message: 'Invalid file type. Please upload Excel (.xlsx, .xls) or CSV (.csv) file' }, { status: 400 } ); } // Validate file size (10MB max) const maxSize = 10 * 1024 * 1024; // 10MB if (file.size > maxSize) { return NextResponse.json( { message: 'File size too large. Maximum size is 10MB' }, { status: 400 } ); } // Read file content const buffer = await file.arrayBuffer(); let data: any[] = []; if (file.type === 'text/csv') { // Handle CSV file const text = new TextDecoder().decode(buffer); const lines = text.split('\n').filter(line => line.trim()); if (lines.length < 2) { return NextResponse.json( { message: 'File must contain at least header and one data row' }, { status: 400 } ); } const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, '')); for (let i = 1; i < lines.length; i++) { const values = lines[i].split(',').map(v => v.trim().replace(/"/g, '')); const row: any = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); data.push(row); } } else { // Handle Excel file const workbook = XLSX.read(buffer, { type: 'buffer' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; data = XLSX.utils.sheet_to_json(worksheet); } if (data.length === 0) { return NextResponse.json( { message: 'No data found in file' }, { status: 400 } ); } // Validate required columns const requiredColumns = ['kode_mk', 'nama_mk', 'sks', 'semester', 'jenis_mk']; const firstRow = data[0]; const missingColumns = requiredColumns.filter(col => !(col in firstRow)); if (missingColumns.length > 0) { return NextResponse.json( { message: `Missing required columns: ${missingColumns.join(', ')}` }, { status: 400 } ); } // Process and validate data const processedData: MataKuliahUpload[] = []; const errors: string[] = []; for (let i = 0; i < data.length; i++) { const row = data[i]; const rowNum = i + 2; // +2 because Excel rows start from 1 and we skip header try { // Validate and convert data types const sks = parseInt(row.sks); const semester = parseInt(row.semester); if (isNaN(sks) || sks <= 0 || sks > 6) { errors.push(`Row ${rowNum}: SKS must be a number between 1-6`); continue; } if (isNaN(semester) || semester <= 0 || semester > 8) { errors.push(`Row ${rowNum}: Semester must be a number between 1-8`); continue; } // Validate jenis_mk const validJenisMK = ['Wajib', 'Pilihan Wajib', 'Pilihan']; if (!validJenisMK.includes(row.jenis_mk)) { errors.push(`Row ${rowNum}: jenis_mk must be one of: ${validJenisMK.join(', ')}`); continue; } // Validate required fields if (!row.kode_mk || !row.nama_mk) { errors.push(`Row ${rowNum}: kode_mk and nama_mk are required`); continue; } processedData.push({ kode_mk: row.kode_mk.toString().trim(), nama_mk: row.nama_mk.toString().trim(), sks, semester, jenis_mk: row.jenis_mk as 'Wajib' | 'Pilihan Wajib' | 'Pilihan', kode_prasyarat: row.kode_prasyarat ? row.kode_prasyarat.toString().trim() : undefined }); } catch (error) { errors.push(`Row ${rowNum}: Invalid data format`); } } if (errors.length > 0) { return NextResponse.json( { message: 'Validation errors found', errors: errors.slice(0, 10) // Limit to first 10 errors }, { status: 400 } ); } // Get existing mata kuliah for duplicate check and prasyarat validation const { data: existingMK, error: fetchError } = await supabase .from('mata_kuliah') .select('kode_mk, id_mk'); if (fetchError) { console.error('Error fetching existing mata kuliah:', fetchError); return NextResponse.json( { message: 'Failed to validate data' }, { status: 500 } ); } const existingCodes = new Set(existingMK?.map(mk => mk.kode_mk) || []); const codeToIdMap = new Map(existingMK?.map(mk => [mk.kode_mk, mk.id_mk]) || []); // Process data for insertion const insertData: any[] = []; const duplicates: string[] = []; for (const mk of processedData) { // Check for duplicates if (existingCodes.has(mk.kode_mk)) { duplicates.push(mk.kode_mk); continue; } // Resolve prasyarat ID let id_prasyarat = null; if (mk.kode_prasyarat) { id_prasyarat = codeToIdMap.get(mk.kode_prasyarat); if (!id_prasyarat) { errors.push(`Prasyarat ${mk.kode_prasyarat} not found for ${mk.kode_mk}`); continue; } } insertData.push({ kode_mk: mk.kode_mk, nama_mk: mk.nama_mk, sks: mk.sks, semester: mk.semester, jenis_mk: mk.jenis_mk, id_prasyarat }); } if (errors.length > 0) { return NextResponse.json( { message: 'Prasyarat validation errors', errors: errors.slice(0, 10) }, { status: 400 } ); } // Insert data to database let successCount = 0; if (insertData.length > 0) { const { error: insertError } = await supabase .from('mata_kuliah') .insert(insertData); if (insertError) { console.error('Error inserting mata kuliah:', insertError); return NextResponse.json( { message: 'Failed to insert data to database' }, { status: 500 } ); } successCount = insertData.length; } // Prepare response const response: any = { message: 'Upload completed', successCount, totalRows: processedData.length }; if (duplicates.length > 0) { response.duplicates = duplicates; response.duplicateCount = duplicates.length; } return NextResponse.json(response); } catch (error) { console.error('Error processing upload:', error); return NextResponse.json( { message: 'Internal server error' }, { status: 500 } ); } }