added calibration-entry-management options
This commit is contained in:
135
src/app/actions/calibration.ts
Normal file
135
src/app/actions/calibration.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
'use server';
|
||||
|
||||
import db from '@/lib/db';
|
||||
import { experiment_calibration } from '@/lib/model/experiment_calibration';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { requireAdmin } from '@/lib/auth';
|
||||
import { unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function getCalibrationEntries(experimentId: number) {
|
||||
try {
|
||||
const entries = await db
|
||||
.select()
|
||||
.from(experiment_calibration)
|
||||
.where(eq(experiment_calibration.experimentId, experimentId));
|
||||
|
||||
return { success: true, data: entries };
|
||||
} catch (error) {
|
||||
console.error('Error fetching calibration entries:', error);
|
||||
throw new Error('Failed to fetch calibration entries');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteCalibrationEntry(calibrationId: number, experimentId: number) {
|
||||
const result = await requireAdmin();
|
||||
if (!result.authenticated || !result.admin) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the entry to find the file path
|
||||
const entries = await db
|
||||
.select()
|
||||
.from(experiment_calibration)
|
||||
.where(
|
||||
and(
|
||||
eq(experiment_calibration.id, calibrationId),
|
||||
eq(experiment_calibration.experimentId, experimentId)
|
||||
)
|
||||
);
|
||||
|
||||
if (entries.length === 0) {
|
||||
throw new Error('Calibration entry not found');
|
||||
}
|
||||
|
||||
const filePath = entries[0].file;
|
||||
|
||||
// Delete from database
|
||||
await db
|
||||
.delete(experiment_calibration)
|
||||
.where(eq(experiment_calibration.id, calibrationId));
|
||||
|
||||
// Delete the audio file
|
||||
try {
|
||||
const fullPath = join(process.cwd(), 'public', filePath);
|
||||
await unlink(fullPath);
|
||||
} catch (err) {
|
||||
console.warn('Could not delete audio file:', err);
|
||||
// Don't fail the operation if file deletion fails
|
||||
}
|
||||
|
||||
revalidatePath(`/admin/experiments/${experimentId}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error deleting calibration entry:', error);
|
||||
throw new Error('Failed to delete calibration entry');
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateCalibrationOrder(experimentId: number, orderMap: Record<number, number>) {
|
||||
const result = await requireAdmin();
|
||||
if (!result.authenticated || !result.admin) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
try {
|
||||
// Update all entries with new order values
|
||||
const updates = Object.entries(orderMap).map(([calibrationId, newOrder]) =>
|
||||
db
|
||||
.update(experiment_calibration)
|
||||
.set({ order: newOrder })
|
||||
.where(
|
||||
and(
|
||||
eq(experiment_calibration.id, parseInt(calibrationId)),
|
||||
eq(experiment_calibration.experimentId, experimentId)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
await Promise.all(updates);
|
||||
revalidatePath(`/admin/experiments/${experimentId}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error updating calibration order:', error);
|
||||
throw new Error('Failed to update calibration order');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAllCalibrationEntries(experimentId: number) {
|
||||
const result = await requireAdmin();
|
||||
if (!result.authenticated || !result.admin) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all entries to delete files
|
||||
const entries = await db
|
||||
.select()
|
||||
.from(experiment_calibration)
|
||||
.where(eq(experiment_calibration.experimentId, experimentId));
|
||||
|
||||
// Delete audio files
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
const fullPath = join(process.cwd(), 'public', entry.file);
|
||||
await unlink(fullPath);
|
||||
} catch (err) {
|
||||
console.warn('Could not delete audio file:', err);
|
||||
// Don't fail the operation if file deletion fails
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all entries from database
|
||||
await db
|
||||
.delete(experiment_calibration)
|
||||
.where(eq(experiment_calibration.experimentId, experimentId));
|
||||
|
||||
revalidatePath(`/admin/experiments/${experimentId}`);
|
||||
return { success: true, deletedCount: entries.length };
|
||||
} catch (error) {
|
||||
console.error('Error deleting all calibration entries:', error);
|
||||
throw new Error('Failed to delete all calibration entries');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import db from '@/lib/db';
|
||||
import { dataset_entry } from '@/lib/model/dataset_entry';
|
||||
import { experiment_calibration } from '@/lib/model/experiment_calibration';
|
||||
import { parseCSVLine } from '@/lib/csv-parser';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { eq } from 'drizzle-orm';
|
||||
@@ -302,3 +303,122 @@ export async function updateDatasetEntriesMetadata(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process calibration audio files for an experiment
|
||||
* Expects metadata.csv with columns: id, audio_file, dialect, order
|
||||
* Audio files should be in a 'wavs' subfolder
|
||||
*/
|
||||
export async function processCalibrationEntries(
|
||||
experimentId: number,
|
||||
tempDir: string
|
||||
) {
|
||||
try {
|
||||
const result = await requireAdmin();
|
||||
if (!result.authenticated || !result.admin) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
entriesCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const calibrationDir = join(process.cwd(), 'public', 'calibration', experimentId.toString());
|
||||
|
||||
// Validate temp directory exists
|
||||
if (!existsSync(tempDir)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Temporary extraction directory not found',
|
||||
entriesCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Parse metadata from directory
|
||||
const { rows } = await parseMetadataFromDirectory(tempDir);
|
||||
|
||||
const entries: typeof experiment_calibration.$inferInsert[] = [];
|
||||
|
||||
// Create calibration directory
|
||||
await mkdir(calibrationDir, { recursive: true });
|
||||
|
||||
// Process each row
|
||||
for (let lineIndex = 0; lineIndex < rows.length; lineIndex++) {
|
||||
const row = rows[lineIndex];
|
||||
const audioFile = row.audio_file as string;
|
||||
// Audio files are in wavs subfolder
|
||||
const audioPath = join(tempDir, audioFile);
|
||||
|
||||
if (existsSync(audioPath)) {
|
||||
// Copy audio file to calibration directory, preserving filename from audio_file
|
||||
const audioBuffer = await readFile(audioPath);
|
||||
const filename = audioFile.split('/').pop() || audioFile;
|
||||
const destPath = join(calibrationDir, filename);
|
||||
|
||||
await writeFile(destPath, audioBuffer);
|
||||
|
||||
entries.push({
|
||||
experimentId,
|
||||
dialectLabel: row.dialect,
|
||||
order: lineIndex + 1,
|
||||
file: `/calibration/${experimentId}/${filename}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'No valid audio files found in wavs folder',
|
||||
entriesCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Check for existing calibration entries to avoid duplicates
|
||||
const existingEntries = await db
|
||||
.select({ file: experiment_calibration.file })
|
||||
.from(experiment_calibration)
|
||||
.where(eq(experiment_calibration.experimentId, experimentId));
|
||||
|
||||
const existingFiles = new Set(existingEntries.map(e => e.file));
|
||||
|
||||
// Filter out duplicates
|
||||
const newEntries = entries.filter(entry => !existingFiles.has(entry.file));
|
||||
|
||||
if (newEntries.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'All calibration entries already exist for this experiment',
|
||||
entriesCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Insert new entries into database
|
||||
await db.insert(experiment_calibration).values(newEntries);
|
||||
|
||||
revalidatePath(`/admin/experiments/${experimentId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
entriesCreated: newEntries.length,
|
||||
message: `Successfully processed and inserted ${newEntries.length} calibration entries.`,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error processing calibration entries:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to process calibration entries';
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
entriesCreated: 0,
|
||||
};
|
||||
} finally {
|
||||
// Clean up temp directory
|
||||
try {
|
||||
if (existsSync(tempDir)) {
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.error('Error cleaning up temp directory:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user