2026-07-21 15:28:55 +08:00
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 c.project_id FROM images i JOIN notes n ON n.id = i.note_id JOIN collections c ON c.id = n.collection_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 ; }
2026-07-21 20:25:52 +08:00
const context = await database . one < { project_id : number ; review_round_id : number ; active_round_id : number | null ; round_status : string ; collection_status : string } > ( 'SELECT c.project_id,v.review_round_id,n.active_round_id,r.status AS round_status,c.status AS collection_status FROM images i JOIN notes n ON n.id=i.note_id JOIN collections c ON c.id=n.collection_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 . collection_status === 'completed' || context . round_status !== 'reviewing' || Number ( context . review_round_id ) !== Number ( context . active_round_id ) ) { res . status ( 409 ) . json ( { error : '历史验收轮次为只读状态' } ) ; return ; }
2026-07-21 15:28:55 +08:00
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' } ) ) ;
} catch ( error ) { next ( error ) ; }
} ) ;
export default router ;