Change Alur Aplikasi

This commit is contained in:
Randa Firman Putra
2025-07-14 15:07:33 +07:00
parent db82b40a6b
commit 6d86e1ca2f
53 changed files with 6109 additions and 964 deletions

View File

@@ -0,0 +1,322 @@
import { NextRequest, NextResponse } from 'next/server';
import supabase from '@/lib/db';
// GET - Fetch all beasiswa mahasiswa or filter by criteria
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const id_mahasiswa = searchParams.get('id_mahasiswa');
const search = searchParams.get('search');
const jenisBeasiswa = searchParams.get('jenis_beasiswa');
// If ID is provided, fetch specific beasiswa by ID with join
if (id) {
const { data, error } = await supabase
.from('beasiswa_mahasiswa')
.select(`
*,
mahasiswa!inner(nama, nim)
`)
.eq('id_beasiswa', id)
.single();
if (error || !data) {
return NextResponse.json({ message: 'Beasiswa mahasiswa not found' }, { status: 404 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = {
...data,
nama: data.mahasiswa.nama,
nim: data.mahasiswa.nim
};
delete transformedData.mahasiswa;
return NextResponse.json(transformedData);
}
// If id_mahasiswa is provided, fetch beasiswa for specific student with join
if (id_mahasiswa) {
const { data, error } = await supabase
.from('beasiswa_mahasiswa')
.select(`
*,
mahasiswa!inner(nama, nim)
`)
.eq('id_mahasiswa', id_mahasiswa)
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = data.map(item => ({
...item,
nama: item.mahasiswa.nama,
nim: item.mahasiswa.nim
})).map(({ mahasiswa, ...rest }) => rest);
return NextResponse.json(transformedData);
}
// Build the query based on filters with join
let query = supabase.from('beasiswa_mahasiswa').select(`
*,
mahasiswa!inner(nama, nim)
`);
// Add search condition if provided
if (search) {
query = query.or(`mahasiswa.nama.ilike.%${search}%,mahasiswa.nim.ilike.%${search}%,nama_beasiswa.ilike.%${search}%,sumber_beasiswa.ilike.%${search}%`);
}
// Add jenis_beasiswa filter if provided
if (jenisBeasiswa) {
query = query.eq('jenis_beasiswa', jenisBeasiswa);
}
// Add order by
query = query.order('created_at', { ascending: false });
// Execute the query
const { data, error } = await query;
if (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = data.map(item => ({
...item,
nama: item.mahasiswa.nama,
nim: item.mahasiswa.nim
})).map(({ mahasiswa, ...rest }) => rest);
return NextResponse.json(transformedData);
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// POST - Create a new beasiswa mahasiswa
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const {
nim,
nama_beasiswa,
sumber_beasiswa,
beasiswa_status,
jenis_beasiswa
} = body;
// Validate required fields
if (!nim || !nama_beasiswa || !sumber_beasiswa || !beasiswa_status || !jenis_beasiswa) {
return NextResponse.json(
{ message: 'Missing required fields: nim, nama_beasiswa, sumber_beasiswa, beasiswa_status, jenis_beasiswa' },
{ status: 400 }
);
}
// Check if mahasiswa exists by NIM and get id_mahasiswa
const { data: mahasiswaExists, error: checkError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (checkError || !mahasiswaExists) {
return NextResponse.json(
{ message: `Mahasiswa dengan NIM ${nim} tidak terdaftar dalam database` },
{ status: 404 }
);
}
// Validate enum values
const validStatus = ['Aktif', 'Selesai', 'Dibatalkan'];
const validJenisBeasiswa = ['Pemerintah', 'Non-Pemerintah'];
if (!validStatus.includes(beasiswa_status)) {
return NextResponse.json(
{ message: 'Invalid beasiswa_status value. Must be one of: Aktif, Selesai, Dibatalkan' },
{ status: 400 }
);
}
if (!validJenisBeasiswa.includes(jenis_beasiswa)) {
return NextResponse.json(
{ message: 'Invalid jenis_beasiswa value. Must be one of: Pemerintah, Non-Pemerintah' },
{ status: 400 }
);
}
// Insert new beasiswa using id_mahasiswa
const { data, error } = await supabase
.from('beasiswa_mahasiswa')
.insert({
id_mahasiswa: mahasiswaExists.id_mahasiswa,
nama_beasiswa,
sumber_beasiswa,
beasiswa_status,
jenis_beasiswa
})
.select()
.single();
if (error) {
console.error('Error creating beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json(
{
message: `Beasiswa berhasil ditambahkan`,
id: data.id_beasiswa
},
{ status: 201 }
);
} catch (error) {
console.error('Error creating beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// PUT - Update an existing beasiswa mahasiswa
export async function PUT(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json({ message: 'ID is required' }, { status: 400 });
}
const body = await request.json();
const {
nim,
nama_beasiswa,
sumber_beasiswa,
beasiswa_status,
jenis_beasiswa
} = body;
// Validate required fields
if (!nim || !nama_beasiswa || !sumber_beasiswa || !beasiswa_status || !jenis_beasiswa) {
return NextResponse.json(
{ message: 'Missing required fields: nim, nama_beasiswa, sumber_beasiswa, beasiswa_status, jenis_beasiswa' },
{ status: 400 }
);
}
// Check if beasiswa exists
const { data: existing, error: checkError } = await supabase
.from('beasiswa_mahasiswa')
.select('*')
.eq('id_beasiswa', id)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Beasiswa mahasiswa not found' }, { status: 404 });
}
// Check if mahasiswa exists by NIM and get id_mahasiswa
const { data: mahasiswaExists, error: mahasiswaCheckError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (mahasiswaCheckError || !mahasiswaExists) {
return NextResponse.json(
{ message: `Mahasiswa dengan NIM ${nim} tidak terdaftar` },
{ status: 404 }
);
}
// Validate enum values
const validStatus = ['Aktif', 'Selesai', 'Dibatalkan'];
const validJenisBeasiswa = ['Pemerintah', 'Non-Pemerintah'];
if (!validStatus.includes(beasiswa_status)) {
return NextResponse.json(
{ message: 'Invalid beasiswa_status value. Must be one of: Aktif, Selesai, Dibatalkan' },
{ status: 400 }
);
}
if (!validJenisBeasiswa.includes(jenis_beasiswa)) {
return NextResponse.json(
{ message: 'Invalid jenis_beasiswa value. Must be one of: Pemerintah, Non-Pemerintah' },
{ status: 400 }
);
}
// Update beasiswa using id_mahasiswa
const { error } = await supabase
.from('beasiswa_mahasiswa')
.update({
id_mahasiswa: mahasiswaExists.id_mahasiswa,
nama_beasiswa,
sumber_beasiswa,
beasiswa_status,
jenis_beasiswa
})
.eq('id_beasiswa', id);
if (error) {
console.error('Error updating beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({
message: `Beasiswa berhasil diperbarui`
});
} catch (error) {
console.error('Error updating beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// DELETE - Delete a beasiswa mahasiswa
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json({ message: 'ID is required' }, { status: 400 });
}
// Check if beasiswa exists
const { data: existing, error: checkError } = await supabase
.from('beasiswa_mahasiswa')
.select('id_beasiswa')
.eq('id_beasiswa', id)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Beasiswa mahasiswa not found' }, { status: 404 });
}
// Delete beasiswa
const { error } = await supabase
.from('beasiswa_mahasiswa')
.delete()
.eq('id_beasiswa', id);
if (error) {
console.error('Error deleting beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({ message: 'Beasiswa mahasiswa deleted successfully' });
} catch (error) {
console.error('Error deleting beasiswa mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}

View File

@@ -0,0 +1,374 @@
import { NextRequest, NextResponse } from 'next/server';
import * as XLSX from 'xlsx';
import supabase from '@/lib/db';
export async function POST(request: NextRequest) {
try {
// Get form data from request
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ message: 'File tidak ditemukan' }, { status: 400 });
}
// Process file data based on file type
let validData = [];
let errors: string[] = [];
if (file.name.endsWith('.csv') || file.type === 'text/csv') {
// Process as CSV
const fileContent = await file.text();
const result = await processCSVData(fileContent);
validData = result.validData;
errors = result.errors;
} else {
// Process as Excel
const fileBuffer = await file.arrayBuffer();
const result = await processExcelData(fileBuffer);
validData = result.validData;
errors = result.errors;
}
if (validData.length === 0) {
return NextResponse.json({
message: 'Tidak ada data valid yang ditemukan dalam file',
errors
}, { status: 400 });
}
// Insert valid data into the database
const { imported, errorCount, errorMessages } = await insertDataToDatabase(validData);
// Combine all error messages
const allErrors = [...errors, ...errorMessages];
return NextResponse.json({
message: 'Upload berhasil',
imported,
errors: errorCount,
errorDetails: allErrors.length > 0 ? allErrors : undefined
});
} catch (error) {
console.error('Error uploading file:', error);
return NextResponse.json(
{ message: `Terjadi kesalahan: ${(error as Error).message}` },
{ status: 500 }
);
}
}
// Function to process Excel data
async function processExcelData(fileBuffer: ArrayBuffer) {
try {
// Parse Excel file
const workbook = XLSX.read(fileBuffer, { type: 'array' });
// Get first sheet
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
// Convert to JSON with proper typing
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as any[][];
if (jsonData.length === 0) {
return { validData: [], errors: ['File Excel kosong'] };
}
// Convert Excel data to CSV-like format for processing
const headers = jsonData[0].map(h => String(h).toLowerCase());
const rows = jsonData.slice(1);
// Process the data using the common function
return processData(headers, rows);
} catch (error) {
console.error('Error processing Excel data:', error);
return { validData: [], errors: [(error as Error).message] };
}
}
// Function to process CSV data
async function processCSVData(fileContent: string) {
const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length === 0) {
return { validData: [], errors: ['File CSV kosong'] };
}
// Get headers from first line
const headerLine = lines[0].toLowerCase();
const headers = headerLine.split(',').map(h => h.trim());
// Process data rows
const rows = lines.slice(1).map(line => line.split(',').map(v => v.trim()));
return processData(headers, rows);
}
// Common function to process data regardless of source format
function processData(headers: string[], rows: any[][]) {
// Define expected headers and their possible variations
const expectedHeaderMap = {
nim: ['nim', 'nomor induk', 'nomor mahasiswa'],
nama_beasiswa: ['nama_beasiswa', 'nama beasiswa', 'namabeasiswa', 'beasiswa', 'nama'],
sumber_beasiswa: ['sumber_beasiswa', 'sumber beasiswa', 'sumberbeasiswa', 'sumber'],
beasiswa_status: ['beasiswa_status', 'status beasiswa', 'statusbeasiswa', 'status'],
jenis_beasiswa: ['jenis_beasiswa', 'jenis beasiswa', 'jenisbeasiswa', 'jenis']
};
// Map actual headers to expected headers
const headerMap: { [key: string]: number } = {};
for (const [expectedHeader, variations] of Object.entries(expectedHeaderMap)) {
const index = headers.findIndex(h =>
variations.some(variation => h.includes(variation))
);
if (index !== -1) {
headerMap[expectedHeader] = index;
}
}
// Check required headers
const requiredHeaders = ['nim', 'nama_beasiswa', 'sumber_beasiswa', 'beasiswa_status', 'jenis_beasiswa'];
const missingHeaders = requiredHeaders.filter(h => headerMap[h] === undefined);
if (missingHeaders.length > 0) {
return {
validData: [],
errors: [`Kolom berikut tidak ditemukan: ${missingHeaders.join(', ')}. Pastikan file memiliki kolom: NIM, Nama Beasiswa, Sumber Beasiswa, Status Beasiswa, dan Jenis Beasiswa.`]
};
}
const validData = [];
const errors = [];
const validStatuses = ['Aktif', 'Selesai', 'Dibatalkan'];
const validJenis = ['Pemerintah', 'Non-Pemerintah'];
// Process data rows
for (let i = 0; i < rows.length; i++) {
const values = rows[i];
if (!values || values.length === 0) continue;
try {
// Extract values using header map
const nim = String(values[headerMap.nim] || '').trim();
const nama_beasiswa = String(values[headerMap.nama_beasiswa] || '').trim();
const sumber_beasiswa = String(values[headerMap.sumber_beasiswa] || '').trim();
let beasiswa_status = String(values[headerMap.beasiswa_status] || '').trim();
let jenis_beasiswa = String(values[headerMap.jenis_beasiswa] || '').trim();
// Validate required fields
if (!nim || !nama_beasiswa || !sumber_beasiswa || !beasiswa_status || !jenis_beasiswa) {
errors.push(`Baris ${i+2}: Data tidak lengkap (NIM: ${nim || 'kosong'})`);
continue;
}
// Normalize status beasiswa
beasiswa_status = normalizeBeasiswaStatus(beasiswa_status);
// Validate status beasiswa
if (!validStatuses.includes(beasiswa_status)) {
errors.push(`Baris ${i+2}: Status beasiswa tidak valid "${beasiswa_status}" untuk NIM ${nim}. Harus salah satu dari: ${validStatuses.join(', ')}`);
continue;
}
// Normalize jenis beasiswa
jenis_beasiswa = normalizeJenisBeasiswa(jenis_beasiswa);
// Validate jenis beasiswa
if (!validJenis.includes(jenis_beasiswa)) {
errors.push(`Baris ${i+2}: Jenis beasiswa tidak valid "${jenis_beasiswa}" untuk NIM ${nim}. Harus salah satu dari: ${validJenis.join(', ')}`);
continue;
}
// Add to valid data
validData.push({
nim,
nama_beasiswa,
sumber_beasiswa,
beasiswa_status,
jenis_beasiswa
});
} catch (error) {
errors.push(`Baris ${i+2}: Error memproses data - ${(error as Error).message}`);
}
}
return { validData, errors };
}
// Function to normalize beasiswa status values
function normalizeBeasiswaStatus(value: string): string {
const lowerValue = value.toLowerCase();
if (['aktif', 'active', 'a'].includes(lowerValue)) {
return 'Aktif';
}
if (['selesai', 'complete', 'completed', 's', 'finish', 'finished'].includes(lowerValue)) {
return 'Selesai';
}
if (['dibatalkan', 'cancel', 'cancelled', 'canceled', 'batal', 'd', 'c'].includes(lowerValue)) {
return 'Dibatalkan';
}
return value; // Return original if no match
}
// Function to normalize jenis beasiswa values
function normalizeJenisBeasiswa(value: string): string {
const lowerValue = value.toLowerCase();
if (['pemerintah', 'government', 'p', 'gov'].includes(lowerValue)) {
return 'Pemerintah';
}
if (['non-pemerintah', 'non pemerintah', 'nonpemerintah', 'swasta', 'private', 'np', 'non'].includes(lowerValue)) {
return 'Non-Pemerintah';
}
return value; // Return original if no match
}
// Function to insert data into database
async function insertDataToDatabase(data: any[]) {
let imported = 0;
let errorCount = 0;
const errorMessages: string[] = [];
console.log('=== DEBUG: Starting beasiswa data insertion process ===');
console.log(`Total data items to process: ${data.length}`);
console.log('Sample data items:', data.slice(0, 3));
// First, validate all NIMs exist before processing
const uniqueNims = [...new Set(data.map(item => item.nim))];
console.log(`Unique NIMs found: ${uniqueNims.length}`);
console.log('Unique NIMs:', uniqueNims);
const nimValidationMap = new Map();
// Batch check all NIMs for existence
console.log('=== DEBUG: Starting NIM validation ===');
for (const nim of uniqueNims) {
try {
console.log(`Checking NIM: ${nim}`);
const { data: mahasiswaData, error: checkError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (checkError || !mahasiswaData) {
console.log(`❌ NIM ${nim}: NOT FOUND in database`);
console.log(`Error details:`, checkError);
nimValidationMap.set(nim, { exists: false, error: 'Mahasiswa dengan NIM ini tidak ditemukan dalam database' });
} else {
console.log(`✅ NIM ${nim}: FOUND - ID: ${mahasiswaData.id_mahasiswa}, Nama: ${mahasiswaData.nama}`);
nimValidationMap.set(nim, { exists: true, id_mahasiswa: mahasiswaData.id_mahasiswa, nama: mahasiswaData.nama });
}
} catch (error) {
console.log(`❌ NIM ${nim}: ERROR during validation`);
console.log(`Error details:`, error);
nimValidationMap.set(nim, { exists: false, error: `Error checking NIM: ${(error as Error).message}` });
}
}
console.log('=== DEBUG: NIM validation results ===');
console.log('Validation map:', Object.fromEntries(nimValidationMap));
// Process each data item
console.log('=== DEBUG: Starting beasiswa data processing ===');
for (const item of data) {
try {
console.log(`\n--- Processing beasiswa item: NIM ${item.nim} ---`);
console.log('Item data:', item);
const nimValidation = nimValidationMap.get(item.nim);
console.log('NIM validation result:', nimValidation);
if (!nimValidation || !nimValidation.exists) {
errorCount++;
const errorMsg = nimValidation?.error || `NIM ${item.nim}: Mahasiswa dengan NIM ini tidak ditemukan dalam database`;
console.log(`❌ Skipping item - ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
}
console.log(`✅ NIM ${item.nim} is valid, proceeding with beasiswa check/insert`);
// Check if beasiswa already exists for this mahasiswa and nama_beasiswa
console.log(`Checking existing beasiswa for mahasiswa ID: ${nimValidation.id_mahasiswa}, nama_beasiswa: ${item.nama_beasiswa}`);
const { data: existingBeasiswa, error: beasiswaCheckError } = await supabase
.from('beasiswa_mahasiswa')
.select('id_beasiswa')
.eq('id_mahasiswa', nimValidation.id_mahasiswa)
.eq('nama_beasiswa', item.nama_beasiswa)
.single();
if (beasiswaCheckError && beasiswaCheckError.code !== 'PGRST116') {
console.log(`❌ Error checking existing beasiswa:`, beasiswaCheckError);
}
if (existingBeasiswa) {
console.log(`📝 Updating existing beasiswa (ID: ${existingBeasiswa.id_beasiswa})`);
// Update existing beasiswa
const { error: updateError } = await supabase
.from('beasiswa_mahasiswa')
.update({
sumber_beasiswa: item.sumber_beasiswa,
beasiswa_status: item.beasiswa_status,
jenis_beasiswa: item.jenis_beasiswa
})
.eq('id_beasiswa', existingBeasiswa.id_beasiswa);
if (updateError) {
errorCount++;
const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal memperbarui beasiswa: ${updateError.message}`;
console.log(`❌ Update failed: ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
} else {
console.log(`✅ Beasiswa updated successfully`);
}
} else {
console.log(`📝 Inserting new beasiswa for mahasiswa ID: ${nimValidation.id_mahasiswa}`);
// Insert new beasiswa
const { error: insertError } = await supabase
.from('beasiswa_mahasiswa')
.insert({
id_mahasiswa: nimValidation.id_mahasiswa,
nama_beasiswa: item.nama_beasiswa,
sumber_beasiswa: item.sumber_beasiswa,
beasiswa_status: item.beasiswa_status,
jenis_beasiswa: item.jenis_beasiswa
});
if (insertError) {
errorCount++;
const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal menyimpan beasiswa: ${insertError.message}`;
console.log(`❌ Insert failed: ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
} else {
console.log(`✅ Beasiswa inserted successfully`);
}
}
imported++;
console.log(`✅ Item processed successfully. Imported count: ${imported}`);
} catch (error) {
console.error(`❌ Error processing record for NIM ${item.nim}:`, error);
errorCount++;
errorMessages.push(`NIM ${item.nim}: Terjadi kesalahan: ${(error as Error).message}`);
}
}
console.log('=== DEBUG: Final results ===');
console.log(`Total imported: ${imported}`);
console.log(`Total errors: ${errorCount}`);
console.log(`Error messages:`, errorMessages);
return { imported, errorCount, errorMessages };
}

View File

@@ -0,0 +1,146 @@
import { NextRequest, NextResponse } from "next/server";
import supabase from "@/lib/db";
// GET - Fetch all kelompok keahlian
export async function GET() {
try {
const { data, error } = await supabase
.from('kelompok_keahlian')
.select('id_kk, nama_kelompok')
.order('nama_kelompok');
if (error) {
throw error;
}
return NextResponse.json(data);
} catch (error) {
console.error("Error fetching kelompok keahlian:", error);
return NextResponse.json(
{ error: "Failed to fetch kelompok keahlian data" },
{ status: 500 }
);
}
}
// POST - Create new kelompok keahlian
export async function POST(request: NextRequest) {
try {
const { nama_kelompok } = await request.json();
// Validation
if (!nama_kelompok) {
return NextResponse.json(
{ error: "nama_kelompok is required" },
{ status: 400 }
);
}
// Check if nama_kelompok already exists
const { data: existingKelompok, error: checkError } = await supabase
.from('kelompok_keahlian')
.select('id_kk')
.eq('nama_kelompok', nama_kelompok)
.single();
if (checkError && checkError.code !== 'PGRST116') {
throw checkError;
}
if (existingKelompok) {
return NextResponse.json(
{ error: "Nama kelompok keahlian already exists" },
{ status: 400 }
);
}
const { data, error } = await supabase
.from('kelompok_keahlian')
.insert([{ nama_kelompok }])
.select('id_kk, nama_kelompok')
.single();
if (error) {
throw error;
}
return NextResponse.json(data, { status: 201 });
} catch (error) {
console.error("Error creating kelompok keahlian:", error);
return NextResponse.json(
{ error: "Failed to create kelompok keahlian" },
{ status: 500 }
);
}
}
// PUT - Update kelompok keahlian
export async function PUT(request: NextRequest) {
try {
const { id_kk, nama_kelompok } = await request.json();
// Validation
if (!id_kk || !nama_kelompok) {
return NextResponse.json(
{ error: "id_kk and nama_kelompok are required" },
{ status: 400 }
);
}
// Check if kelompok keahlian exists
const { data: existingKelompok, error: checkError } = await supabase
.from('kelompok_keahlian')
.select('id_kk')
.eq('id_kk', id_kk)
.single();
if (checkError && checkError.code !== 'PGRST116') {
throw checkError;
}
if (!existingKelompok) {
return NextResponse.json(
{ error: "Kelompok keahlian not found" },
{ status: 404 }
);
}
// Check if nama_kelompok already exists for another kelompok
const { data: duplicateNama, error: duplicateError } = await supabase
.from('kelompok_keahlian')
.select('id_kk')
.eq('nama_kelompok', nama_kelompok)
.neq('id_kk', id_kk)
.single();
if (duplicateError && duplicateError.code !== 'PGRST116') {
throw duplicateError;
}
if (duplicateNama) {
return NextResponse.json(
{ error: "Nama kelompok keahlian already exists for another kelompok" },
{ status: 400 }
);
}
const { data, error } = await supabase
.from('kelompok_keahlian')
.update({ nama_kelompok })
.eq('id_kk', id_kk)
.select('id_kk, nama_kelompok')
.single();
if (error) {
throw error;
}
return NextResponse.json(data);
} catch (error) {
console.error("Error updating kelompok keahlian:", error);
return NextResponse.json(
{ error: "Failed to update kelompok keahlian" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,241 @@
import { NextRequest, NextResponse } from 'next/server';
import supabase from '@/lib/db';
// GET - Fetch all mahasiswa or a specific one by NIM
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const nim = searchParams.get('nim');
if (nim) {
// Fetch specific mahasiswa by NIM with joins
const { data, error } = await supabase
.from('mahasiswa')
.select(`
*,
kelompok_keahlian!id_kelompok_keahlian(id_kk, nama_kelompok)
`)
.eq('nim', nim)
.single();
if (error || !data) {
return NextResponse.json({ message: 'Mahasiswa not found' }, { status: 404 });
}
// Transform the data to flatten the joined fields
const transformedData = {
...data,
nama_kelompok_keahlian: data.kelompok_keahlian?.nama_kelompok || null
};
delete transformedData.kelompok_keahlian;
return NextResponse.json(transformedData);
} else {
// Fetch all mahasiswa with joins
const { data, error } = await supabase
.from('mahasiswa')
.select(`
*,
kelompok_keahlian!id_kelompok_keahlian(id_kk, nama_kelompok)
`)
.order('nim');
if (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Transform the data to flatten the joined fields
const transformedData = data.map(item => ({
...item,
nama_kelompok_keahlian: item.kelompok_keahlian?.nama_kelompok || null
})).map(({ kelompok_keahlian, ...rest }) => rest);
return NextResponse.json(transformedData);
}
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// POST - Create a new mahasiswa
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const {
nim,
nama,
jk,
agama,
kabupaten,
provinsi,
jenis_pendaftaran,
tahun_angkatan,
ipk,
id_kelompok_keahlian,
status_kuliah,
semester
} = body;
// Validate required fields
if (!nim || !nama || !jk || !tahun_angkatan) {
return NextResponse.json(
{ message: 'Missing required fields: nim, nama, jk, tahun_angkatan' },
{ status: 400 }
);
}
// Check if mahasiswa already exists
const { data: existing, error: checkError } = await supabase
.from('mahasiswa')
.select('nim')
.eq('nim', nim)
.single();
if (existing) {
return NextResponse.json(
{ message: 'Mahasiswa with this NIM already exists' },
{ status: 409 }
);
}
// Insert new mahasiswa
const { data, error } = await supabase
.from('mahasiswa')
.insert({
nim,
nama,
jk,
agama: agama || null,
kabupaten: kabupaten || null,
provinsi: provinsi || null,
jenis_pendaftaran: jenis_pendaftaran || null,
tahun_angkatan,
ipk: ipk || null,
id_kelompok_keahlian: id_kelompok_keahlian || null,
status_kuliah: status_kuliah || "Aktif",
semester: semester || 1
})
.select()
.single();
if (error) {
console.error('Error creating mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json(
{ message: 'Mahasiswa created successfully', nim },
{ status: 201 }
);
} catch (error) {
console.error('Error creating mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// PUT - Update an existing mahasiswa
export async function PUT(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const nim = searchParams.get('nim');
if (!nim) {
return NextResponse.json({ message: 'NIM is required' }, { status: 400 });
}
const body = await request.json();
const {
nama,
jk,
agama,
kabupaten,
provinsi,
jenis_pendaftaran,
tahun_angkatan,
ipk,
id_kelompok_keahlian,
status_kuliah,
semester
} = body;
// Check if mahasiswa exists
const { data: existing, error: checkError } = await supabase
.from('mahasiswa')
.select('*')
.eq('nim', nim)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Mahasiswa not found' }, { status: 404 });
}
// Update mahasiswa
const { error } = await supabase
.from('mahasiswa')
.update({
nama: nama || existing.nama,
jk: jk || existing.jk,
agama: agama || existing.agama,
kabupaten: kabupaten || existing.kabupaten,
provinsi: provinsi || existing.provinsi,
jenis_pendaftaran: jenis_pendaftaran || existing.jenis_pendaftaran,
tahun_angkatan: tahun_angkatan || existing.tahun_angkatan,
ipk: ipk || existing.ipk,
id_kelompok_keahlian: id_kelompok_keahlian || existing.id_kelompok_keahlian,
status_kuliah: status_kuliah || existing.status_kuliah,
semester: semester || existing.semester
})
.eq('nim', nim);
if (error) {
console.error('Error updating mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({ message: 'Mahasiswa updated successfully', nim });
} catch (error) {
console.error('Error updating mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// DELETE - Delete a mahasiswa
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const nim = searchParams.get('nim');
if (!nim) {
return NextResponse.json({ message: 'NIM is required' }, { status: 400 });
}
// Check if mahasiswa exists
const { data: existing, error: checkError } = await supabase
.from('mahasiswa')
.select('nim')
.eq('nim', nim)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Mahasiswa not found' }, { status: 404 });
}
// Delete mahasiswa
const { error } = await supabase
.from('mahasiswa')
.delete()
.eq('nim', nim);
if (error) {
console.error('Error deleting mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({ message: 'Mahasiswa deleted successfully' });
} catch (error) {
console.error('Error deleting mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}

View File

@@ -0,0 +1,360 @@
import { NextRequest, NextResponse } from 'next/server';
import supabase from '@/lib/db';
import * as XLSX from 'xlsx';
export async function POST(request: NextRequest) {
try {
// Get form data from request
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ message: 'No file uploaded' }, { status: 400 });
}
// Read file content as array buffer
const fileBuffer = await file.arrayBuffer();
// Process file data based on file type
let validData = [];
let errors: string[] = [];
if (file.name.endsWith('.csv') || file.type === 'text/csv') {
// Process as CSV
const fileContent = await file.text();
const result = await processCSVData(fileContent);
validData = result.validData;
errors = result.errors;
} else {
// Process as Excel
const result = await processExcelData(fileBuffer);
validData = result.validData;
errors = result.errors;
}
if (validData.length === 0) {
return NextResponse.json({
message: 'No valid data found in the file',
errors
}, { status: 400 });
}
// Insert valid data into the database
const { insertedCount, errorCount } = await insertDataToDatabase(validData);
return NextResponse.json({
message: 'File processed successfully',
insertedCount,
errorCount,
errors: errors.length > 0 ? errors : undefined
});
} catch (error) {
console.error('Error processing file upload:', error);
return NextResponse.json({
message: 'Error processing file upload',
error: (error as Error).message
}, { status: 500 });
}
}
// Function to process Excel data
async function processExcelData(fileBuffer: ArrayBuffer) {
try {
// Parse Excel file
const workbook = XLSX.read(fileBuffer, { type: 'array' });
// Get first sheet
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
// Convert to JSON with proper typing
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as any[][];
if (jsonData.length === 0) {
return { validData: [], errors: ['Excel file is empty'] };
}
// Convert Excel data to CSV-like format for processing
const headers = jsonData[0].map(h => String(h).toLowerCase());
const rows = jsonData.slice(1);
// Process the data using the common function
return processData(headers, rows);
} catch (error) {
console.error('Error processing Excel data:', error);
return { validData: [], errors: [(error as Error).message] };
}
}
// Function to process CSV data
async function processCSVData(fileContent: string) {
const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length === 0) {
return { validData: [], errors: ['CSV file is empty'] };
}
// Get headers from first line
const headerLine = lines[0].toLowerCase();
const headers = headerLine.split(',').map(h => h.trim());
// Process data rows
const rows = lines.slice(1).map(line => line.split(',').map(v => v.trim()));
return processData(headers, rows);
}
// Common function to process data regardless of source format
function processData(headers: string[], rows: any[][]) {
// Define expected headers and their possible variations
const expectedHeaderMap = {
nim: ['nim'],
nama: ['nama', 'name'],
jenis_kelamin: ['jenis_kelamin', 'jk', 'gender'],
agama: ['agama', 'religion'],
kabupaten: ['kabupaten', 'kota', 'city'],
provinsi: ['provinsi', 'province'],
jenis_pendaftaran: ['jenis_pendaftaran', 'jalur_masuk', 'admission_type'],
tahun_angkatan: ['tahun_angkatan', 'angkatan', 'tahun', 'year'],
ipk: ['ipk', 'gpa'],
kelompok_keahlian: ['kelompok_keahlian', 'kk', 'keahlian', 'id_kk'],
status_kuliah: ['status_kuliah', 'status', 'status_mahasiswa'],
semester: ['semester', 'sem']
};
// Map actual headers to expected headers
const headerMap: { [key: string]: number } = {};
for (const [expectedHeader, variations] of Object.entries(expectedHeaderMap)) {
const index = headers.findIndex(h => variations.includes(h));
if (index !== -1) {
headerMap[expectedHeader] = index;
}
}
// Check required headers
const requiredHeaders = ['nim', 'nama', 'jenis_kelamin', 'tahun_angkatan'];
const missingHeaders = requiredHeaders.filter(h => headerMap[h] === undefined);
if (missingHeaders.length > 0) {
return {
validData: [],
errors: [`Missing required headers: ${missingHeaders.join(', ')}`]
};
}
const validData = [];
const errors = [];
// Process data rows
for (let i = 0; i < rows.length; i++) {
const values = rows[i];
if (!values || values.length === 0) continue;
try {
// Extract values using header map
const nim = String(values[headerMap.nim] || '');
const nama = String(values[headerMap.nama] || '');
const jenis_kelamin = mapGender(String(values[headerMap.jenis_kelamin] || ''));
const tahun_angkatan = String(values[headerMap.tahun_angkatan] || '');
// Optional fields
const agama = headerMap.agama !== undefined ? String(values[headerMap.agama] || '') || null : null;
const kabupaten = headerMap.kabupaten !== undefined ? String(values[headerMap.kabupaten] || '') || null : null;
const provinsi = headerMap.provinsi !== undefined ? String(values[headerMap.provinsi] || '') || null : null;
const jenis_pendaftaran = headerMap.jenis_pendaftaran !== undefined ? String(values[headerMap.jenis_pendaftaran] || '') || null : null;
// Handle IPK (could be number or string)
let ipk = null;
if (headerMap.ipk !== undefined && values[headerMap.ipk] !== undefined && values[headerMap.ipk] !== null) {
const ipkValue = values[headerMap.ipk];
const ipkStr = typeof ipkValue === 'number' ? ipkValue.toString() : String(ipkValue);
ipk = ipkStr ? parseFloat(ipkStr) : null;
}
// Handle kelompok_keahlian (could be number or string)
let kelompok_keahlian_id = null;
if (headerMap.kelompok_keahlian !== undefined && values[headerMap.kelompok_keahlian] !== undefined) {
const kkValue = values[headerMap.kelompok_keahlian];
if (kkValue !== null && kkValue !== '') {
kelompok_keahlian_id = typeof kkValue === 'number' ? kkValue : parseInt(String(kkValue));
}
}
// Handle status_kuliah
let status_kuliah = 'Aktif'; // Default value
if (headerMap.status_kuliah !== undefined && values[headerMap.status_kuliah] !== undefined) {
const statusValue = String(values[headerMap.status_kuliah] || '').trim();
if (statusValue) {
const mappedStatus = mapStatus(statusValue);
if (mappedStatus) {
status_kuliah = mappedStatus;
}
}
}
// Handle semester (could be number or string)
let semester = 1; // Default value
if (headerMap.semester !== undefined && values[headerMap.semester] !== undefined) {
const semesterValue = values[headerMap.semester];
if (semesterValue !== null && semesterValue !== '') {
const semesterNum = typeof semesterValue === 'number' ? semesterValue : parseInt(String(semesterValue));
if (!isNaN(semesterNum) && semesterNum >= 1 && semesterNum <= 14) {
semester = semesterNum;
}
}
}
// Validate required fields
if (!nim || !nama || !jenis_kelamin || !tahun_angkatan) {
errors.push(`Row ${i+1}: Missing required fields`);
continue;
}
// Validate NIM format (should be alphanumeric and proper length)
if (!/^[A-Za-z0-9]{8,12}$/.test(nim)) {
errors.push(`Row ${i+1}: Invalid NIM format - ${nim}`);
continue;
}
// Validate tahun_angkatan format (should be 4 digits)
if (!/^\d{4}$/.test(tahun_angkatan)) {
errors.push(`Row ${i+1}: Invalid tahun_angkatan format - ${tahun_angkatan}`);
continue;
}
// Validate IPK if provided
if (ipk !== null && (isNaN(ipk) || ipk < 0 || ipk > 4)) {
errors.push(`Row ${i+1}: Invalid IPK value - ${ipk}`);
continue;
}
// Validate semester if provided
if (semester < 1 || semester > 14) {
errors.push(`Row ${i+1}: Invalid semester value - ${semester} (must be between 1-14)`);
continue;
}
// Add to valid data
validData.push({
nim,
nama,
jk: jenis_kelamin,
agama,
kabupaten,
provinsi,
jenis_pendaftaran,
tahun_angkatan,
ipk,
kelompok_keahlian_id,
status_kuliah,
semester
});
} catch (error) {
errors.push(`Row ${i+1}: Error processing row - ${(error as Error).message}`);
}
}
return { validData, errors };
}
// Function to map gender values to standardized format
function mapGender(value: string): 'Pria' | 'Wanita' | null {
if (!value) return null;
const lowerValue = value.toLowerCase();
if (['pria', 'laki-laki', 'laki', 'l', 'male', 'm', 'p'].includes(lowerValue)) {
return 'Pria';
}
if (['wanita', 'perempuan', 'w', 'female', 'f', 'woman', 'w'].includes(lowerValue)) {
return 'Wanita';
}
return null;
}
// Function to map status values to standardized format
function mapStatus(value: string): 'Aktif' | 'Cuti' | 'Lulus' | 'Non-Aktif' | null {
if (!value) return null;
const lowerValue = value.toLowerCase();
if (['aktif', 'active', 'a'].includes(lowerValue)) {
return 'Aktif';
}
if (['cuti', 'leave', 'c'].includes(lowerValue)) {
return 'Cuti';
}
if (['lulus', 'graduated', 'graduate', 'l'].includes(lowerValue)) {
return 'Lulus';
}
if (['non-aktif', 'non aktif', 'nonaktif', 'non-aktif', 'non aktif', 'nonaktif', 'n'].includes(lowerValue)) {
return 'Non-Aktif';
}
return null;
}
// Function to insert data into database
async function insertDataToDatabase(data: any[]) {
let insertedCount = 0;
let errorCount = 0;
for (const item of data) {
try {
// Check if mahasiswa already exists
const { data: existingData } = await supabase
.from('mahasiswa')
.select('nim')
.eq('nim', item.nim)
.single();
const mahasiswaData = {
nama: item.nama,
jk: item.jk,
agama: item.agama,
kabupaten: item.kabupaten,
provinsi: item.provinsi,
jenis_pendaftaran: item.jenis_pendaftaran,
tahun_angkatan: item.tahun_angkatan,
ipk: item.ipk,
id_kelompok_keahlian: item.kelompok_keahlian_id,
status_kuliah: item.status_kuliah,
semester: item.semester
};
if (existingData) {
// Update existing record
const { error } = await supabase
.from('mahasiswa')
.update(mahasiswaData)
.eq('nim', item.nim);
if (error) throw error;
} else {
// Insert new record
const { error } = await supabase
.from('mahasiswa')
.insert({
nim: item.nim,
...mahasiswaData
});
if (error) throw error;
}
insertedCount++;
} catch (error) {
console.error(`Error inserting/updating record for NIM ${item.nim}:`, error);
errorCount++;
}
}
return { insertedCount, errorCount };
}

View File

@@ -0,0 +1,336 @@
import { NextRequest, NextResponse } from 'next/server';
import supabase from '@/lib/db';
// GET - Fetch all prestasi mahasiswa or filter by criteria
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const id_mahasiswa = searchParams.get('id_mahasiswa');
const search = searchParams.get('search');
const jenisPrestasi = searchParams.get('jenis_prestasi');
const tingkat = searchParams.get('tingkat');
// If ID is provided, fetch specific prestasi by ID with join
if (id) {
const { data, error } = await supabase
.from('prestasi_mahasiswa')
.select(`
*,
mahasiswa!inner(nama, nim)
`)
.eq('id_prestasi', id)
.single();
if (error || !data) {
return NextResponse.json({ message: 'Prestasi mahasiswa not found' }, { status: 404 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = {
...data,
nama: data.mahasiswa.nama,
nim: data.mahasiswa.nim
};
delete transformedData.mahasiswa;
return NextResponse.json(transformedData);
}
// If id_mahasiswa is provided, fetch prestasi for specific student with join
if (id_mahasiswa) {
const { data, error } = await supabase
.from('prestasi_mahasiswa')
.select(`
*,
mahasiswa!inner(nama, nim)
`)
.eq('id_mahasiswa', id_mahasiswa)
.order('tanggal_prestasi', { ascending: false });
if (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = data.map(item => ({
...item,
nama: item.mahasiswa.nama,
nim: item.mahasiswa.nim
})).map(({ mahasiswa, ...rest }) => rest);
return NextResponse.json(transformedData);
}
// Build the query based on filters with join
let query = supabase.from('prestasi_mahasiswa').select(`
*,
mahasiswa!inner(nama, nim)
`);
// Add search condition if provided
if (search) {
query = query.or(`mahasiswa.nama.ilike.%${search}%,mahasiswa.nim.ilike.%${search}%,nama_prestasi.ilike.%${search}%,peringkat.ilike.%${search}%,keterangan.ilike.%${search}%`);
}
// Add jenis_prestasi filter if provided
if (jenisPrestasi) {
query = query.eq('jenis_prestasi', jenisPrestasi);
}
// Add tingkat filter if provided
if (tingkat) {
query = query.eq('tingkat_prestasi', tingkat);
}
// Add order by
query = query.order('tanggal_prestasi', { ascending: false });
// Execute the query
const { data, error } = await query;
if (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Transform the data to flatten the nama and nim fields
const transformedData = data.map(item => ({
...item,
nama: item.mahasiswa.nama,
nim: item.mahasiswa.nim
})).map(({ mahasiswa, ...rest }) => rest);
return NextResponse.json(transformedData);
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// POST - Create a new prestasi mahasiswa
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const {
nim,
jenis_prestasi,
nama_prestasi,
tingkat_prestasi,
peringkat,
tanggal_prestasi,
keterangan
} = body;
// Validate required fields
if (!nim || !jenis_prestasi || !nama_prestasi || !tingkat_prestasi || !peringkat || !tanggal_prestasi) {
return NextResponse.json(
{ message: 'Missing required fields: nim, jenis_prestasi, nama_prestasi, tingkat_prestasi, peringkat, tanggal_prestasi' },
{ status: 400 }
);
}
// Check if mahasiswa exists by NIM and get id_mahasiswa
const { data: mahasiswaExists, error: checkError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (checkError || !mahasiswaExists) {
return NextResponse.json(
{ message: `Mahasiswa dengan NIM ${nim} tidak terdaftar` },
{ status: 404 }
);
}
// Validate enum values
const validJenisPrestasi = ['Akademik', 'Non-Akademik'];
const validTingkat = ['Kabupaten', 'Provinsi', 'Nasional', 'Internasional'];
if (!validJenisPrestasi.includes(jenis_prestasi)) {
return NextResponse.json(
{ message: 'Invalid jenis_prestasi value. Must be one of: Akademik, Non-Akademik' },
{ status: 400 }
);
}
if (!validTingkat.includes(tingkat_prestasi)) {
return NextResponse.json(
{ message: 'Invalid tingkat_prestasi value. Must be one of: Kabupaten, Provinsi, Nasional, Internasional' },
{ status: 400 }
);
}
// Insert new prestasi using id_mahasiswa
const { data, error } = await supabase
.from('prestasi_mahasiswa')
.insert({
id_mahasiswa: mahasiswaExists.id_mahasiswa,
jenis_prestasi,
nama_prestasi,
tingkat_prestasi,
peringkat,
tanggal_prestasi,
keterangan: keterangan || null
})
.select()
.single();
if (error) {
console.error('Error creating prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json(
{
message: `Prestasi berhasil ditambahkan`,
id: data.id_prestasi
},
{ status: 201 }
);
} catch (error) {
console.error('Error creating prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// PUT - Update an existing prestasi mahasiswa
export async function PUT(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json({ message: 'ID is required' }, { status: 400 });
}
const body = await request.json();
const {
nim,
jenis_prestasi,
nama_prestasi,
tingkat_prestasi,
peringkat,
tanggal_prestasi,
keterangan
} = body;
// Validate required fields
if (!nim || !jenis_prestasi || !nama_prestasi || !tingkat_prestasi || !peringkat || !tanggal_prestasi) {
return NextResponse.json(
{ message: 'Missing required fields: nim, jenis_prestasi, nama_prestasi, tingkat_prestasi, peringkat, tanggal_prestasi' },
{ status: 400 }
);
}
// Check if prestasi exists
const { data: existing, error: checkError } = await supabase
.from('prestasi_mahasiswa')
.select('*')
.eq('id_prestasi', id)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Prestasi mahasiswa not found' }, { status: 404 });
}
// Check if mahasiswa exists by NIM and get id_mahasiswa
const { data: mahasiswaExists, error: mahasiswaCheckError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (mahasiswaCheckError || !mahasiswaExists) {
return NextResponse.json(
{ message: `Mahasiswa dengan NIM ${nim} tidak terdaftar` },
{ status: 404 }
);
}
// Validate enum values
const validJenisPrestasi = ['Akademik', 'Non-Akademik'];
const validTingkat = ['Kabupaten', 'Provinsi', 'Nasional', 'Internasional'];
if (!validJenisPrestasi.includes(jenis_prestasi)) {
return NextResponse.json(
{ message: 'Invalid jenis_prestasi value. Must be one of: Akademik, Non-Akademik' },
{ status: 400 }
);
}
if (!validTingkat.includes(tingkat_prestasi)) {
return NextResponse.json(
{ message: 'Invalid tingkat_prestasi value. Must be one of: Kabupaten, Provinsi, Nasional, Internasional' },
{ status: 400 }
);
}
// Update prestasi using id_mahasiswa
const { error } = await supabase
.from('prestasi_mahasiswa')
.update({
id_mahasiswa: mahasiswaExists.id_mahasiswa,
jenis_prestasi,
nama_prestasi,
tingkat_prestasi,
peringkat,
tanggal_prestasi,
keterangan: keterangan || null
})
.eq('id_prestasi', id);
if (error) {
console.error('Error updating prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({
message: `Prestasi berhasil diperbarui`
});
} catch (error) {
console.error('Error updating prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// DELETE - Delete a prestasi mahasiswa
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json({ message: 'ID is required' }, { status: 400 });
}
// Check if prestasi exists
const { data: existing, error: checkError } = await supabase
.from('prestasi_mahasiswa')
.select('id_prestasi')
.eq('id_prestasi', id)
.single();
if (checkError || !existing) {
return NextResponse.json({ message: 'Prestasi mahasiswa not found' }, { status: 404 });
}
// Delete prestasi
const { error } = await supabase
.from('prestasi_mahasiswa')
.delete()
.eq('id_prestasi', id);
if (error) {
console.error('Error deleting prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({ message: 'Prestasi mahasiswa deleted successfully' });
} catch (error) {
console.error('Error deleting prestasi mahasiswa:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}

View File

@@ -0,0 +1,470 @@
import { NextRequest, NextResponse } from 'next/server';
import * as XLSX from 'xlsx';
import supabase from '@/lib/db';
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: 'File tidak ditemukan' }, { status: 400 });
}
let validData = [];
let errors: string[] = [];
if (file.name.endsWith('.csv') || file.type === 'text/csv') {
const fileContent = await file.text();
const result = await processCSVData(fileContent);
validData = result.validData;
errors = result.errors;
} else {
const fileBuffer = await file.arrayBuffer();
const result = await processExcelData(fileBuffer);
validData = result.validData;
errors = result.errors;
}
if (validData.length === 0) {
return NextResponse.json({
message: 'Tidak ada data valid yang ditemukan dalam file',
errors
}, { status: 400 });
}
const { imported, errorCount, errorMessages } = await insertDataToDatabase(validData);
const allErrors = [...errors, ...errorMessages];
return NextResponse.json({
message: 'Upload berhasil',
imported,
errors: errorCount,
errorDetails: allErrors.length > 0 ? allErrors : undefined
});
} catch (error) {
console.error('Error uploading file:', error);
return NextResponse.json(
{ message: `Terjadi kesalahan: ${(error as Error).message}` },
{ status: 500 }
);
}
}
async function processExcelData(fileBuffer: ArrayBuffer) {
try {
const workbook = XLSX.read(fileBuffer, {
type: 'array',
cellDates: true,
dateNF: 'yyyy-mm-dd'
});
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
let jsonData = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
raw: false,
dateNF: 'yyyy-mm-dd'
}) as any[][];
if (jsonData.length === 0) {
return { validData: [], errors: ['File Excel kosong'] };
}
jsonData = jsonData.map(row => {
if (!row) return row;
return row.map(cell => {
if (cell && typeof cell === 'object' && 'toISOString' in cell) {
return cell.toISOString().split('T')[0];
}
return cell;
});
});
const headers = jsonData[0].map(h => String(h).toLowerCase());
const rows = jsonData.slice(1);
return processData(headers, rows);
} catch (error) {
return { validData: [], errors: [(error as Error).message] };
}
}
async function processCSVData(fileContent: string) {
const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length === 0) {
return { validData: [], errors: ['File CSV kosong'] };
}
const headerLine = lines[0].toLowerCase();
const headers = headerLine.split(',').map(h => h.trim());
const rows = lines.slice(1).map(line => line.split(',').map(v => v.trim()));
return processData(headers, rows);
}
function processData(headers: string[], rows: any[][]) {
const expectedHeaderMap = {
nim: ['nim', 'nomor induk', 'nomor mahasiswa'],
jenis_prestasi: ['jenis prestasi', 'jenis_prestasi', 'jenisprestasi'],
nama_prestasi: ['nama prestasi', 'nama_prestasi', 'namaprestasi', 'prestasi'],
tingkat_prestasi: ['tingkat prestasi', 'tingkat_prestasi', 'tingkatprestasi', 'tingkat'],
peringkat: ['peringkat', 'ranking', 'juara', 'posisi'],
tanggal_prestasi: ['tanggal prestasi', 'tanggal_prestasi', 'tanggalprestasi', 'tanggal']
};
const headerMap: { [key: string]: number } = {};
for (const [expectedHeader, variations] of Object.entries(expectedHeaderMap)) {
const index = headers.findIndex(h => {
if (!h) return false;
const headerStr = String(h).toLowerCase().trim();
return variations.some(variation => headerStr === variation);
});
if (index !== -1) {
headerMap[expectedHeader] = index;
}
}
for (const [expectedHeader, variations] of Object.entries(expectedHeaderMap)) {
if (headerMap[expectedHeader] !== undefined) continue;
const index = headers.findIndex(h => {
if (!h) return false;
const headerStr = String(h).toLowerCase().trim();
return variations.some(variation => headerStr.includes(variation));
});
if (index !== -1) {
headerMap[expectedHeader] = index;
}
}
const requiredHeaders = ['nim', 'jenis_prestasi', 'nama_prestasi', 'tingkat_prestasi', 'peringkat', 'tanggal_prestasi'];
const missingHeaders = requiredHeaders.filter(h => headerMap[h] === undefined);
if (missingHeaders.length > 0) {
return {
validData: [],
errors: [`Kolom berikut tidak ditemukan: ${missingHeaders.join(', ')}. Pastikan file memiliki kolom: NIM, Jenis Prestasi, Nama Prestasi, Tingkat Prestasi, Peringkat, dan Tanggal.`]
};
}
const validData = [];
const errors = [];
const validJenisPrestasi = ['Akademik', 'Non-Akademik'];
const validTingkatPrestasi = ['Kabupaten', 'Provinsi', 'Nasional', 'Internasional'];
for (let i = 0; i < rows.length; i++) {
const values = rows[i];
if (!values || values.length === 0) continue;
try {
const nim = String(values[headerMap.nim] || '').trim();
let jenis_prestasi = String(values[headerMap.jenis_prestasi] || '').trim();
const nama_prestasi = String(values[headerMap.nama_prestasi] || '').trim();
let tingkat_prestasi = String(values[headerMap.tingkat_prestasi] || '').trim();
const peringkat = String(values[headerMap.peringkat] || '').trim();
let tanggal_prestasi = String(values[headerMap.tanggal_prestasi] || '').trim();
if (!nim || !jenis_prestasi || !nama_prestasi || !tingkat_prestasi || !peringkat || !tanggal_prestasi) {
const errorMsg = `Baris ${i+2}: Data tidak lengkap (NIM: ${nim || 'kosong'})`;
errors.push(errorMsg);
continue;
}
jenis_prestasi = normalizeJenisPrestasi(jenis_prestasi);
if (!validJenisPrestasi.includes(jenis_prestasi)) {
const errorMsg = `Baris ${i+2}: Jenis prestasi tidak valid "${jenis_prestasi}" untuk NIM ${nim}. Harus salah satu dari: ${validJenisPrestasi.join(', ')}`;
errors.push(errorMsg);
continue;
}
tingkat_prestasi = normalizeTingkatPrestasi(tingkat_prestasi);
if (!validTingkatPrestasi.includes(tingkat_prestasi)) {
const errorMsg = `Baris ${i+2}: Tingkat prestasi tidak valid "${tingkat_prestasi}" untuk NIM ${nim}. Harus salah satu dari: ${validTingkatPrestasi.join(', ')}`;
errors.push(errorMsg);
continue;
}
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
if (!datePattern.test(tanggal_prestasi)) {
try {
const ddmmyyyyPattern = /^(\d{1,2})-(\d{1,2})-(\d{4})$/;
const ddmmyyyyMatch = tanggal_prestasi.match(ddmmyyyyPattern);
const ddmmyyyySlashPattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
const ddmmyyyySlashMatch = tanggal_prestasi.match(ddmmyyyySlashPattern);
if (ddmmyyyyMatch) {
const day = ddmmyyyyMatch[1].padStart(2, '0');
const month = ddmmyyyyMatch[2].padStart(2, '0');
const year = ddmmyyyyMatch[3];
if (parseInt(year) < 1900 || parseInt(year) > 2100) {
const errorMsg = `Baris ${i+2}: Tahun tidak valid "${year}" untuk NIM ${nim}. Tahun harus antara 1900-2100`;
errors.push(errorMsg);
continue;
}
tanggal_prestasi = `${year}-${month}-${day}`;
}
else if (ddmmyyyySlashMatch) {
const day = ddmmyyyySlashMatch[1].padStart(2, '0');
const month = ddmmyyyySlashMatch[2].padStart(2, '0');
const year = ddmmyyyySlashMatch[3];
if (parseInt(year) < 1900 || parseInt(year) > 2100) {
const errorMsg = `Baris ${i+2}: Tahun tidak valid "${year}" untuk NIM ${nim}. Tahun harus antara 1900-2100`;
errors.push(errorMsg);
continue;
}
tanggal_prestasi = `${year}-${month}-${day}`;
}
else {
const numericValue = Number(tanggal_prestasi);
if (!isNaN(numericValue)) {
let dateObj;
if (numericValue > 60) {
const adjustedValue = numericValue - 1;
const daysToMs = adjustedValue * 24 * 60 * 60 * 1000;
dateObj = new Date(new Date(1899, 11, 30).getTime() + daysToMs);
} else {
const daysToMs = numericValue * 24 * 60 * 60 * 1000;
dateObj = new Date(new Date(1899, 11, 30).getTime() + daysToMs);
}
if (isValidDate(dateObj)) {
tanggal_prestasi = dateObj.toISOString().split('T')[0];
} else {
const errorMsg = `Baris ${i+2}: Format tanggal tidak valid "${tanggal_prestasi}" untuk NIM ${nim}. Tahun harus antara 1900-2100`;
errors.push(errorMsg);
continue;
}
} else {
const dateObj = new Date(tanggal_prestasi);
if (!isValidDate(dateObj)) {
const errorMsg = `Baris ${i+2}: Format tanggal tidak valid "${tanggal_prestasi}" untuk NIM ${nim}. Gunakan format DD-MM-YYYY, DD/MM/YYYY, atau YYYY-MM-DD`;
errors.push(errorMsg);
continue;
}
tanggal_prestasi = dateObj.toISOString().split('T')[0];
}
}
} catch (e) {
const errorMsg = `Baris ${i+2}: Format tanggal tidak valid "${tanggal_prestasi}" untuk NIM ${nim}. Gunakan format DD-MM-YYYY, DD/MM/YYYY, atau YYYY-MM-DD`;
errors.push(errorMsg);
continue;
}
}
validData.push({
nim,
jenis_prestasi,
nama_prestasi,
tingkat_prestasi,
peringkat,
tanggal_prestasi,
keterangan: null
});
} catch (error) {
const errorMsg = `Baris ${i+2}: Error memproses data - ${(error as Error).message}`;
errors.push(errorMsg);
}
}
return { validData, errors };
}
function normalizeJenisPrestasi(value: string): string {
const lowerValue = value.toLowerCase();
if (['akademik', 'academic', 'akademis', 'a'].includes(lowerValue)) {
return 'Akademik';
}
if (['non-akademik', 'non akademik', 'nonakademik', 'non academic', 'na', 'n'].includes(lowerValue)) {
return 'Non-Akademik';
}
return value;
}
function normalizeTingkatPrestasi(value: string): string {
const lowerValue = value.toLowerCase();
if (['kabupaten', 'kota', 'city', 'kab', 'k'].includes(lowerValue)) {
return 'Kabupaten';
}
if (['provinsi', 'province', 'prov', 'p'].includes(lowerValue)) {
return 'Provinsi';
}
if (['nasional', 'national', 'nas', 'n'].includes(lowerValue)) {
return 'Nasional';
}
if (['internasional', 'international', 'int', 'i'].includes(lowerValue)) {
return 'Internasional';
}
return value;
}
function isValidDate(date: Date): boolean {
return !isNaN(date.getTime()) &&
date.getFullYear() >= 1900 &&
date.getFullYear() <= 2100;
}
async function insertDataToDatabase(data: any[]) {
let imported = 0;
let errorCount = 0;
const errorMessages: string[] = [];
console.log('=== DEBUG: Starting prestasi data insertion process ===');
console.log(`Total data items to process: ${data.length}`);
console.log('Sample data items:', data.slice(0, 3));
// First, validate all NIMs exist before processing
const uniqueNims = [...new Set(data.map(item => item.nim))];
console.log(`Unique NIMs found: ${uniqueNims.length}`);
console.log('Unique NIMs:', uniqueNims);
const nimValidationMap = new Map();
// Batch check all NIMs for existence
console.log('=== DEBUG: Starting NIM validation ===');
for (const nim of uniqueNims) {
try {
console.log(`Checking NIM: ${nim}`);
const { data: mahasiswaData, error: checkError } = await supabase
.from('mahasiswa')
.select('id_mahasiswa, nama')
.eq('nim', nim)
.single();
if (checkError || !mahasiswaData) {
console.log(`❌ NIM ${nim}: NOT FOUND in database`);
console.log(`Error details:`, checkError);
nimValidationMap.set(nim, { exists: false, error: 'Mahasiswa dengan NIM ini tidak ditemukan dalam database' });
} else {
console.log(`✅ NIM ${nim}: FOUND - ID: ${mahasiswaData.id_mahasiswa}, Nama: ${mahasiswaData.nama}`);
nimValidationMap.set(nim, { exists: true, id_mahasiswa: mahasiswaData.id_mahasiswa, nama: mahasiswaData.nama });
}
} catch (error) {
console.log(`❌ NIM ${nim}: ERROR during validation`);
console.log(`Error details:`, error);
nimValidationMap.set(nim, { exists: false, error: `Error checking NIM: ${(error as Error).message}` });
}
}
console.log('=== DEBUG: NIM validation results ===');
console.log('Validation map:', Object.fromEntries(nimValidationMap));
// Process each data item
console.log('=== DEBUG: Starting prestasi data processing ===');
for (const item of data) {
try {
console.log(`\n--- Processing prestasi item: NIM ${item.nim} ---`);
console.log('Item data:', item);
const nimValidation = nimValidationMap.get(item.nim);
console.log('NIM validation result:', nimValidation);
if (!nimValidation || !nimValidation.exists) {
errorCount++;
const errorMsg = nimValidation?.error || `NIM ${item.nim}: Mahasiswa dengan NIM ini tidak ditemukan dalam database`;
console.log(`❌ Skipping item - ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
}
console.log(`✅ NIM ${item.nim} is valid, proceeding with prestasi check/insert`);
// Check if prestasi already exists for this mahasiswa
console.log(`Checking existing prestasi for mahasiswa ID: ${nimValidation.id_mahasiswa}, nama_prestasi: ${item.nama_prestasi}, tanggal: ${item.tanggal_prestasi}`);
const { data: existingPrestasi, error: prestasiCheckError } = await supabase
.from('prestasi_mahasiswa')
.select('id_prestasi')
.eq('id_mahasiswa', nimValidation.id_mahasiswa)
.eq('nama_prestasi', item.nama_prestasi)
.eq('tanggal_prestasi', item.tanggal_prestasi)
.single();
if (prestasiCheckError && prestasiCheckError.code !== 'PGRST116') {
console.log(`❌ Error checking existing prestasi:`, prestasiCheckError);
}
if (existingPrestasi) {
console.log(`📝 Updating existing prestasi (ID: ${existingPrestasi.id_prestasi})`);
// Update existing prestasi
const { error: updateError } = await supabase
.from('prestasi_mahasiswa')
.update({
jenis_prestasi: item.jenis_prestasi,
tingkat_prestasi: item.tingkat_prestasi,
peringkat: item.peringkat,
keterangan: item.keterangan || null
})
.eq('id_prestasi', existingPrestasi.id_prestasi);
if (updateError) {
errorCount++;
const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal memperbarui prestasi: ${updateError.message}`;
console.log(`❌ Update failed: ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
} else {
console.log(`✅ Prestasi updated successfully`);
}
} else {
console.log(`📝 Inserting new prestasi for mahasiswa ID: ${nimValidation.id_mahasiswa}`);
// Insert new prestasi
const { error: insertError } = await supabase
.from('prestasi_mahasiswa')
.insert({
id_mahasiswa: nimValidation.id_mahasiswa,
jenis_prestasi: item.jenis_prestasi,
nama_prestasi: item.nama_prestasi,
tingkat_prestasi: item.tingkat_prestasi,
peringkat: item.peringkat,
tanggal_prestasi: item.tanggal_prestasi,
keterangan: item.keterangan || null
});
if (insertError) {
errorCount++;
const errorMsg = `NIM ${item.nim} (${nimValidation.nama}): Gagal menyimpan prestasi: ${insertError.message}`;
console.log(`❌ Insert failed: ${errorMsg}`);
errorMessages.push(errorMsg);
continue;
} else {
console.log(`✅ Prestasi inserted successfully`);
}
}
imported++;
console.log(`✅ Item processed successfully. Imported count: ${imported}`);
} catch (error) {
console.error(`❌ Error processing record for NIM ${item.nim}:`, error);
errorCount++;
errorMessages.push(`NIM ${item.nim}: Terjadi kesalahan: ${(error as Error).message}`);
}
}
console.log('=== DEBUG: Final results ===');
console.log(`Total imported: ${imported}`);
console.log(`Total errors: ${errorCount}`);
console.log(`Error messages:`, errorMessages);
return { imported, errorCount, errorMessages };
}

View File

@@ -0,0 +1,63 @@
import { NextRequest, NextResponse } from 'next/server';
import supabase from '@/lib/db';
// GET - Fetch all unique jenis_pendaftaran values
export async function GET() {
try {
// Get all unique jenis_pendaftaran values
const { data, error } = await supabase
.from('mahasiswa')
.select('jenis_pendaftaran')
.not('jenis_pendaftaran', 'is', null)
.order('jenis_pendaftaran');
if (error) {
console.error('Error fetching jenis pendaftaran data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
// Get unique values
const uniqueValues = [...new Set(data.map(item => item.jenis_pendaftaran))];
return NextResponse.json(uniqueValues.map(value => ({ jenis_pendaftaran: value })));
} catch (error) {
console.error('Error fetching jenis pendaftaran data:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}
// PUT - Update jenis_pendaftaran value
export async function PUT(request: NextRequest) {
try {
const body = await request.json();
const { oldValue, newValue } = body;
// Validate required fields
if (!oldValue || !newValue) {
return NextResponse.json(
{ message: 'Missing required fields: oldValue, newValue' },
{ status: 400 }
);
}
// Update jenis_pendaftaran
const { data, error } = await supabase
.from('mahasiswa')
.update({ jenis_pendaftaran: newValue })
.eq('jenis_pendaftaran', oldValue)
.select();
if (error) {
console.error('Error updating jenis pendaftaran:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
return NextResponse.json({
message: 'Jenis pendaftaran updated successfully',
affectedRows: data?.length || 0
});
} catch (error) {
console.error('Error updating jenis pendaftaran:', error);
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}

View File

@@ -0,0 +1,94 @@
import { NextResponse } from "next/server";
import supabase from '@/lib/db';
export async function POST() {
try {
// Get current date
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1; // getMonth() returns 0-11
// Get all active students
const { data: activeStudents, error: fetchError } = await supabase
.from('mahasiswa')
.select('nim, tahun_angkatan, semester')
.eq('status_kuliah', 'Aktif');
if (fetchError) {
console.error('Error fetching active students:', fetchError);
return NextResponse.json(
{ message: "Gagal mengambil data mahasiswa aktif" },
{ status: 500 }
);
}
if (!activeStudents || activeStudents.length === 0) {
return NextResponse.json({
message: "Tidak ada mahasiswa aktif yang ditemukan",
affectedRows: 0
});
}
let updatedCount = 0;
const errors: string[] = [];
// Update semester for each active student
for (const student of activeStudents) {
try {
const tahunAngkatan = student.tahun_angkatan;
if (!tahunAngkatan) {
errors.push(`Mahasiswa NIM ${student.nim}: Tahun angkatan tidak ditemukan`);
continue;
}
// Calculate current semester based on tahun_angkatan and current date
const yearsSinceEnrollment = currentYear - tahunAngkatan;
let currentSemester = yearsSinceEnrollment * 2; // 2 semesters per year
// Adjust for current month (odd months = odd semesters, even months = even semesters)
if (currentMonth >= 2 && currentMonth <= 7) {
// February to July = odd semester (1, 3, 5, etc.)
currentSemester += 1;
} else {
// August to January = even semester (2, 4, 6, etc.)
currentSemester += 2;
}
// Cap at semester 14 (7 years)
if (currentSemester > 14) {
currentSemester = 14;
}
// Update semester if different
if (student.semester !== currentSemester) {
const { error: updateError } = await supabase
.from('mahasiswa')
.update({ semester: currentSemester })
.eq('nim', student.nim);
if (updateError) {
errors.push(`Mahasiswa NIM ${student.nim}: Gagal memperbarui semester: ${updateError.message}`);
} else {
updatedCount++;
}
}
} catch (error) {
console.error(`Error updating semester for mahasiswa NIM ${student.nim}:`, error);
errors.push(`Mahasiswa NIM ${student.nim}: Terjadi kesalahan: ${(error as Error).message}`);
}
}
return NextResponse.json({
message: `Berhasil memperbarui semester untuk ${updatedCount} mahasiswa`,
affectedRows: updatedCount,
errors: errors.length > 0 ? errors : undefined
});
} catch (error) {
console.error('Error in update semester:', error);
return NextResponse.json(
{ message: "Terjadi kesalahan internal server" },
{ status: 500 }
);
}
}