add services/mam-api/src/routes/assets.js
This commit is contained in:
parent
8e247fe2ce
commit
5cb9ccaefc
1 changed files with 245 additions and 0 deletions
245
services/mam-api/src/routes/assets.js
Normal file
245
services/mam-api/src/routes/assets.js
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
import express from 'express';
|
||||
import pool from '../db/pool.js';
|
||||
import { getSignedUrlForObject, deleteObject } from '../s3/client.js';
|
||||
import { requireAuth } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(requireAuth);
|
||||
|
||||
// GET / - List assets with filtering
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const {
|
||||
project_id,
|
||||
bin_id,
|
||||
status,
|
||||
search,
|
||||
media_type,
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
} = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT a.*,
|
||||
COUNT(*) OVER() AS full_count
|
||||
FROM assets a
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
let paramCount = 1;
|
||||
|
||||
if (project_id) {
|
||||
query += ` AND a.project_id = $${paramCount++}`;
|
||||
params.push(project_id);
|
||||
}
|
||||
|
||||
if (bin_id) {
|
||||
query += ` AND a.bin_id = $${paramCount++}`;
|
||||
params.push(bin_id);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
query += ` AND a.status = $${paramCount++}`;
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
if (media_type) {
|
||||
query += ` AND a.media_type = $${paramCount++}`;
|
||||
params.push(media_type);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
query += ` AND (a.display_name ILIKE $${paramCount} OR a.notes ILIKE $${paramCount})`;
|
||||
params.push(`%${search}%`);
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
query += ` ORDER BY a.created_at DESC`;
|
||||
query += ` LIMIT $${paramCount++} OFFSET $${paramCount++}`;
|
||||
params.push(limit, offset);
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
const total = result.rows.length > 0 ? result.rows[0].full_count : 0;
|
||||
|
||||
res.json({
|
||||
assets: result.rows,
|
||||
total,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /:id - Single asset
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// PATCH /:id - Update asset
|
||||
router.patch('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { display_name, tags, notes } = req.body;
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
let paramCount = 1;
|
||||
|
||||
if (display_name !== undefined) {
|
||||
updates.push(`display_name = $${paramCount++}`);
|
||||
params.push(display_name);
|
||||
}
|
||||
|
||||
if (tags !== undefined) {
|
||||
updates.push(`tags = $${paramCount++}`);
|
||||
params.push(tags);
|
||||
}
|
||||
|
||||
if (notes !== undefined) {
|
||||
updates.push(`notes = $${paramCount++}`);
|
||||
params.push(notes);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'No fields to update' });
|
||||
}
|
||||
|
||||
updates.push(`updated_at = NOW()`);
|
||||
params.push(id);
|
||||
|
||||
const query = `
|
||||
UPDATE assets
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${paramCount}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /:id - Soft or hard delete
|
||||
router.delete('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { hard } = req.query;
|
||||
|
||||
if (hard === 'true') {
|
||||
// Hard delete: get asset info, delete from S3, delete from DB
|
||||
const assetResult = await pool.query(
|
||||
'SELECT * FROM assets WHERE id = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (assetResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
const asset = assetResult.rows[0];
|
||||
|
||||
// Delete from S3
|
||||
if (asset.proxy_s3_key) {
|
||||
await deleteObject(asset.proxy_s3_key);
|
||||
}
|
||||
if (asset.thumbnail_s3_key) {
|
||||
await deleteObject(asset.thumbnail_s3_key);
|
||||
}
|
||||
if (asset.original_s3_key) {
|
||||
await deleteObject(asset.original_s3_key);
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
await pool.query('DELETE FROM assets WHERE id = $1', [id]);
|
||||
|
||||
res.json({ message: 'Asset deleted permanently' });
|
||||
} else {
|
||||
// Soft delete: set status to archived
|
||||
const result = await pool.query(
|
||||
'UPDATE assets SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
|
||||
['archived', id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /:id/stream - Signed URL for proxy playback
|
||||
router.get('/:id/stream', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await pool.query(
|
||||
'SELECT proxy_s3_key FROM assets WHERE id = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
const { proxy_s3_key } = result.rows[0];
|
||||
|
||||
if (!proxy_s3_key) {
|
||||
return res.status(400).json({ error: 'No proxy available' });
|
||||
}
|
||||
|
||||
const url = await getSignedUrlForObject(proxy_s3_key);
|
||||
res.json({ url });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /:id/thumbnail - Signed URL for thumbnail
|
||||
router.get('/:id/thumbnail', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await pool.query(
|
||||
'SELECT thumbnail_s3_key FROM assets WHERE id = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Asset not found' });
|
||||
}
|
||||
|
||||
const { thumbnail_s3_key } = result.rows[0];
|
||||
|
||||
if (!thumbnail_s3_key) {
|
||||
return res.status(400).json({ error: 'No thumbnail available' });
|
||||
}
|
||||
|
||||
const url = await getSignedUrlForObject(thumbnail_s3_key);
|
||||
res.json({ url });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Loading…
Reference in a new issue