dragonflight/services/mam-api/src/routes/comments.js
ZGaetano 4172b0d70a rip out entire auth/login flow
- remove requireAuth from all route files
- delete auth.js, tokens.js, users.js routes
- delete auth middleware
- remove session middleware and all auth deps from index.js
- delete login.html and auth-guard.js from web-ui
2026-05-27 03:39:58 +00:00

113 lines
3.8 KiB
JavaScript

// Asset-scoped comments for the Asset Detail page.
//
// Mounted at /api/v1/assets/:assetId/comments via app.use('/api/v1/assets/:assetId/comments', router).
// Express's :assetId param flows through from the parent mount.
import express from 'express';
import pool from '../db/pool.js';
const router = express.Router({ mergeParams: true });
function rowToJson(r) {
return {
id: r.id,
asset_id: r.asset_id,
user_id: r.user_id,
body: r.body,
frame_ms: r.frame_ms,
resolved: r.resolved,
created_at: r.created_at,
updated_at: r.updated_at,
author_name: r.author_name || null,
author_initials: r.author_initials || null,
};
}
// GET /api/v1/assets/:assetId/comments
router.get('/', async (req, res, next) => {
try {
const { assetId } = req.params;
const result = await pool.query(
`SELECT c.*,
u.display_name AS author_name,
UPPER(SUBSTRING(COALESCE(u.display_name, u.username, '?'), 1, 2)) AS author_initials
FROM asset_comments c
LEFT JOIN users u ON u.id = c.user_id
WHERE c.asset_id = $1
ORDER BY c.created_at ASC`,
[assetId]
);
res.json({ comments: result.rows.map(rowToJson) });
} catch (err) { next(err); }
});
// POST /api/v1/assets/:assetId/comments
router.post('/', async (req, res, next) => {
try {
const { assetId } = req.params;
const { body, frame_ms } = req.body || {};
if (!body || !String(body).trim()) {
return res.status(400).json({ error: 'body is required' });
}
// Best-effort author lookup — pull from the session if AUTH_ENABLED is on.
const userId = req.session?.userId || null;
const ins = await pool.query(
`INSERT INTO asset_comments (asset_id, user_id, body, frame_ms)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[assetId, userId, String(body).trim(), frame_ms != null ? Math.max(0, Math.round(Number(frame_ms))) : null]
);
// Re-fetch with the author join so the response has the same shape as list.
const result = await pool.query(
`SELECT c.*,
u.display_name AS author_name,
UPPER(SUBSTRING(COALESCE(u.display_name, u.username, '?'), 1, 2)) AS author_initials
FROM asset_comments c
LEFT JOIN users u ON u.id = c.user_id
WHERE c.id = $1`,
[ins.rows[0].id]
);
res.status(201).json(rowToJson(result.rows[0]));
} catch (err) { next(err); }
});
// PATCH /api/v1/assets/:assetId/comments/:id
router.patch('/:id', async (req, res, next) => {
try {
const { id, assetId } = req.params;
const { body, resolved } = req.body || {};
const fields = [];
const values = [];
let i = 1;
if (body !== undefined) { fields.push(`body = $${i++}`); values.push(String(body).trim()); }
if (resolved !== undefined) { fields.push(`resolved = $${i++}`); values.push(!!resolved); }
if (fields.length === 0) return res.status(400).json({ error: 'Nothing to update' });
fields.push('updated_at = NOW()');
values.push(id, assetId);
const result = await pool.query(
`UPDATE asset_comments SET ${fields.join(', ')}
WHERE id = $${i++} AND asset_id = $${i}
RETURNING *`,
values
);
if (result.rows.length === 0) return res.status(404).json({ error: 'Comment not found' });
res.json(rowToJson(result.rows[0]));
} catch (err) { next(err); }
});
// DELETE /api/v1/assets/:assetId/comments/:id
router.delete('/:id', async (req, res, next) => {
try {
const { id, assetId } = req.params;
const result = await pool.query(
`DELETE FROM asset_comments WHERE id = $1 AND asset_id = $2 RETURNING id`,
[id, assetId]
);
if (result.rows.length === 0) return res.status(404).json({ error: 'Comment not found' });
res.json({ id });
} catch (err) { next(err); }
});
export default router;