Files
taptapp/src/routes/admin/audio/+page.server.js
2025-07-25 15:37:12 +02:00

200 lines
4.6 KiB
JavaScript

import { fail } from '@sveltejs/kit';
import { db } from '$lib/server/db/index.js';
import { audioFile } from '$lib/server/db/schema.js';
import { eq, isNull } from 'drizzle-orm';
import { uploadToS3, deleteFromS3, generateAudioS3Key } from '$lib/server/s3.js';
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';
const execAsync = promisify(exec);
async function getAudioDuration(buffer) {
try {
// Create temporary file
const tempDir = os.tmpdir();
const tempFilePath = path.join(tempDir, `audio_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`);
// Write buffer to temp file
await fs.writeFile(tempFilePath, buffer);
try {
// Use ffprobe to get duration
const { stdout } = await execAsync(`ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${tempFilePath}"`);
const duration = parseFloat(stdout.trim());
// Clean up temp file
await fs.unlink(tempFilePath);
return isNaN(duration) ? null : duration;
} catch (error) {
// Clean up temp file even if ffprobe fails
try {
await fs.unlink(tempFilePath);
} catch {}
console.error('Error extracting audio duration:', error);
return null;
}
} catch (error) {
console.error('Error creating temp file for duration extraction:', error);
return null;
}
}
export async function load() {
const audioFiles = await db.select({
id: audioFile.id,
filename: audioFile.filename,
contentType: audioFile.contentType,
duration: audioFile.duration,
fileSize: audioFile.fileSize,
createdAt: audioFile.createdAt
})
.from(audioFile)
.where(isNull(audioFile.deletedAt)); // Only show active audio files
return {
audioFiles
};
}
export const actions = {
upload: async ({ request }) => {
const data = await request.formData();
const file = data.get('audioFile');
if (!file || file.size === 0) {
return fail(400, {
missing: true
});
}
if (!file.type.startsWith('audio/')) {
return fail(400, {
invalidType: true
});
}
const id = crypto.randomUUID();
const buffer = Buffer.from(await file.arrayBuffer());
const s3Key = generateAudioS3Key(id, file.name);
try {
// Extract duration before uploading
const duration = await getAudioDuration(buffer);
// Upload to S3 first
await uploadToS3(s3Key, buffer, file.type);
// Then save metadata to database with calculated duration
await db.insert(audioFile).values({
id,
filename: file.name,
contentType: file.type,
s3Key,
duration,
fileSize: file.size,
createdAt: new Date()
});
return {
success: true
};
} catch (error) {
console.error('Error uploading audio file:', error);
return fail(500, {
error: error.message || 'Upload failed'
});
}
},
delete: async ({ request }) => {
const data = await request.formData();
const fileId = data.get('fileId');
if (!fileId) {
return fail(400, {
missing: true
});
}
try {
// Soft delete from database (don't delete from S3 for recovery purposes)
await db
.update(audioFile)
.set({ deletedAt: new Date() })
.where(eq(audioFile.id, fileId));
return {
deleted: true
};
} catch (error) {
console.error('Error deleting audio file:', error);
return fail(500, {
error: true
});
}
},
updateDuration: async ({ request }) => {
const data = await request.formData();
const fileId = data.get('fileId');
const duration = parseFloat(data.get('duration'));
if (!fileId || isNaN(duration)) {
return fail(400, {
missing: true
});
}
try {
await db.update(audioFile)
.set({ duration })
.where(eq(audioFile.id, fileId));
return {
success: true
};
} catch (error) {
console.error('Error updating duration:', error);
return fail(500, {
error: true
});
}
},
renameAudioFile: async ({ request }) => {
const data = await request.formData();
const audioFileId = data.get('audioFileId');
const newFilename = data.get('newFilename');
if (!audioFileId || !newFilename) {
return fail(400, { error: 'Invalid data provided' });
}
// Validate filename
const filename = newFilename.trim();
if (filename.length === 0) {
return fail(400, { error: 'Filename cannot be empty' });
}
if (filename.length > 255) {
return fail(400, { error: 'Filename is too long' });
}
try {
await db
.update(audioFile)
.set({ filename })
.where(eq(audioFile.id, audioFileId));
return { renamed: true, filename };
} catch (error) {
console.error('Error renaming audio file:', error);
return fail(500, { error: 'Failed to rename audio file' });
}
}
};