41 lines
3.2 KiB
TypeScript
41 lines
3.2 KiB
TypeScript
import { Router, type Response, type NextFunction } from 'express';
|
|
import { imagesRepository } from '../repositories/imagesRepository.js';
|
|
import { annotationsRepository } from '../repositories/annotationsRepository.js';
|
|
import { canWriteProject, requireWriter, type AuthRequest } from '../auth.js';
|
|
import { database } from '../database.js';
|
|
|
|
const router = Router();
|
|
|
|
async function imageProjectId(imageId: number): Promise<number | undefined> {
|
|
return (await database.one<{ project_id: number }>('SELECT n.project_id FROM images i JOIN notes n ON n.id = i.note_id WHERE i.id = ?', [imageId]))?.project_id;
|
|
}
|
|
|
|
router.get('/:imageId/annotations', requireWriter, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const imageId = Number(req.params.imageId);
|
|
if (!Number.isFinite(imageId)) { res.status(400).json({ error: '无效的图片 ID' }); return; }
|
|
const projectId = await imageProjectId(imageId);
|
|
if (!projectId) { res.status(404).json({ error: '图片不存在' }); return; }
|
|
if (!await canWriteProject(req, projectId)) { res.status(403).json({ error: '无权查看该图片' }); return; }
|
|
res.json(await annotationsRepository.listByImage(imageId));
|
|
} catch (error) { next(error); }
|
|
});
|
|
|
|
router.post('/:imageId/annotations', requireWriter, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const imageId = Number(req.params.imageId);
|
|
if (!Number.isFinite(imageId)) { res.status(400).json({ error: '无效的图片 ID' }); return; }
|
|
if (!await imagesRepository.findById(imageId)) { res.status(404).json({ error: '图片不存在' }); return; }
|
|
const context = await database.one<{ project_id: number; review_round_id: number; active_round_id: number | null; round_status: string; project_review_status: string; project_status: string }>('SELECT n.project_id,v.review_round_id,n.active_round_id,r.status AS round_status,p.review_status AS project_review_status,p.status AS project_status FROM images i JOIN notes n ON n.id=i.note_id JOIN projects p ON p.id=n.project_id JOIN work_versions v ON v.note_id=i.note_id AND v.version_number=i.version_number JOIN review_rounds r ON r.id=v.review_round_id WHERE i.id=?', [imageId]);
|
|
if (!context || !await canWriteProject(req, context.project_id)) { res.status(403).json({ error: '无权操作该图片' }); return; }
|
|
if (context.project_status !== 'active' || context.project_review_status === 'completed' || context.round_status !== 'reviewing' || Number(context.review_round_id) !== Number(context.active_round_id)) { res.status(409).json({ error: '历史轮次、已关闭或已完成项目为只读状态' }); return; }
|
|
const { x, y } = req.body ?? {};
|
|
const content = String(req.body?.content ?? '').trim();
|
|
if (typeof x !== 'number' || typeof y !== 'number' || x < 0 || x > 1 || y < 0 || y > 1) { res.status(400).json({ error: '批注坐标无效' }); return; }
|
|
if (!content) { res.status(400).json({ error: '批注内容不能为空' }); return; }
|
|
res.status(201).json(await annotationsRepository.create(imageId, { x, y, content, author_name: req.authUser?.display_name || 'API', author_role: 'operator' }));
|
|
} catch (error) { next(error); }
|
|
});
|
|
|
|
export default router;
|