Initial commit: ACE-Step UI - Open source music generation interface
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user