Files
ace-step-ui/server/src/routes/referenceTrack.ts
T

181 lines
5.4 KiB
TypeScript

import { Router, Response } from 'express';
import multer from 'multer';
import path from 'path';
import { pool } from '../db/pool.js';
import { authMiddleware, AuthenticatedRequest } from '../middleware/auth.js';
import { getStorageProvider } from '../services/storage/factory.js';
const router = Router();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB max
fileFilter: (_req, file, cb) => {
const allowedTypes = ['audio/mpeg', 'audio/wav', 'audio/flac', 'audio/mp3', 'audio/x-wav', 'audio/x-flac'];
if (allowedTypes.includes(file.mimetype) || file.originalname.match(/\.(mp3|wav|flac)$/i)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only MP3, WAV, and FLAC are allowed.'));
}
}
});
// Get user's reference tracks
router.get('/', authMiddleware, async (req: AuthenticatedRequest, res: Response) => {
try {
const result = await pool.query(
`SELECT id, filename, storage_key, duration, file_size_bytes, tags, created_at
FROM reference_tracks
WHERE user_id = $1
ORDER BY created_at DESC`,
[req.user!.id]
);
const storage = getStorageProvider();
const tracks = result.rows.map(row => ({
...row,
audio_url: storage.getPublicUrl(row.storage_key)
}));
res.json({ tracks });
} catch (error) {
console.error('Get reference tracks error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Upload a new reference track
router.post('/', authMiddleware, upload.single('audio'), async (req: AuthenticatedRequest, res: Response) => {
try {
if (!req.file) {
res.status(400).json({ error: 'No file uploaded' });
return;
}
const userId = req.user!.id;
const originalFilename = req.file.originalname;
const ext = path.extname(originalFilename) || '.mp3';
const timestamp = Date.now();
const key = `reference-tracks/${userId}/${timestamp}${ext}`;
const storage = getStorageProvider();
await storage.upload(key, req.file.buffer, req.file.mimetype);
const audioUrl = storage.getPublicUrl(key);
// Parse tags from request body if provided
const tags = req.body.tags ? JSON.parse(req.body.tags) : null;
const result = await pool.query(
`INSERT INTO reference_tracks (user_id, filename, storage_key, file_size_bytes, tags)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[userId, originalFilename, key, req.file.size, tags]
);
res.status(201).json({
track: {
...result.rows[0],
audio_url: audioUrl
}
});
} catch (error) {
console.error('Upload reference track error:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: 'Failed to upload reference track', details: errorMessage });
}
});
// Update reference track (duration, tags)
router.patch('/:id', authMiddleware, async (req: AuthenticatedRequest, res: Response) => {
try {
// Verify ownership
const check = await pool.query(
'SELECT user_id FROM reference_tracks WHERE id = $1',
[req.params.id]
);
if (check.rows.length === 0) {
res.status(404).json({ error: 'Track not found' });
return;
}
if (check.rows[0].user_id !== req.user!.id) {
res.status(403).json({ error: 'Access denied' });
return;
}
const { duration, tags } = req.body;
const updates: string[] = [];
const values: unknown[] = [];
let paramCount = 1;
if (duration !== undefined) {
updates.push(`duration = $${paramCount}`);
values.push(duration);
paramCount++;
}
if (tags !== undefined) {
updates.push(`tags = $${paramCount}`);
values.push(tags);
paramCount++;
}
if (updates.length === 0) {
res.status(400).json({ error: 'No fields to update' });
return;
}
values.push(req.params.id);
const result = await pool.query(
`UPDATE reference_tracks SET ${updates.join(', ')} WHERE id = $${paramCount} RETURNING *`,
values
);
const storage = getStorageProvider();
res.json({
track: {
...result.rows[0],
audio_url: storage.getPublicUrl(result.rows[0].storage_key)
}
});
} catch (error) {
console.error('Update reference track error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Delete a reference track
router.delete('/:id', authMiddleware, async (req: AuthenticatedRequest, res: Response) => {
try {
// Verify ownership
const check = await pool.query(
'SELECT user_id, storage_key FROM reference_tracks WHERE id = $1',
[req.params.id]
);
if (check.rows.length === 0) {
res.status(404).json({ error: 'Track not found' });
return;
}
if (check.rows[0].user_id !== req.user!.id) {
res.status(403).json({ error: 'Access denied' });
return;
}
// Delete from storage
const storage = getStorageProvider();
try {
await storage.delete(check.rows[0].storage_key);
} catch (storageError) {
console.error('Failed to delete from storage:', storageError);
}
// Delete from database
await pool.query('DELETE FROM reference_tracks WHERE id = $1', [req.params.id]);
res.json({ success: true });
} catch (error) {
console.error('Delete reference track error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
export default router;