Files
ihaleist/server/document-review.mjs

62 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs';
import path from 'node:path';
import {createHash} from 'node:crypto';
import {openDb,BELGE_DIR} from './user-store.mjs';
const hash=value=>createHash('sha256').update(value).digest('hex');
const fail=(status,message)=>Object.assign(new Error(message),{status});
function source(db,email,id) {
const record=db.prepare('SELECT * FROM belgeler WHERE id=? AND email=?').get(id,email);
if(!record || record.tur !== 'ihale') throw fail(404,'İhale dokümanı bulunamadı.');
let bytes;
try {bytes=fs.readFileSync(path.join(BELGE_DIR,record.dosya));}
catch {throw fail(409,'Kaynak dosya okunamıyor; inceleme kaydedilemez.');}
return hash(bytes);
}
function latest(db,email,id) {
return db.prepare('SELECT revision,file_hash,text_hash,corrected_text,review_note,reviewed_at FROM document_text_reviews WHERE document_id=? AND email=? ORDER BY revision DESC LIMIT 1').get(id,email) || null;
}
export function documentReview(email,id) {
const db=openDb();
try {
const fileHash=source(db,email,id), review=latest(db,email,id);
return {documentId:id,fileHash,review,stale:Boolean(review && review.file_hash!==fileHash)};
} finally {db.close();}
}
export function saveDocumentReview(email,id,input) {
if(typeof input.correctedText !== 'string' || input.correctedText.trim().length<40 || Buffer.byteLength(input.correctedText)>1024*1024)
throw fail(400,'Kontrol edilen metin 40 karakter ile 1 MB arasında olmalı.');
if(typeof input.note !== 'string' || input.note.trim().length<20 || input.note.length>2000)
throw fail(400,'Karşılaştırılan sayfaları ve yapılan düzeltmeleri en az 20 karakterle açıklayın.');
if(input.allPagesCompared !== true) throw fail(400,'Tüm kaynak sayfalarıyla karşılaştırma gerekli.');
if(!Number.isInteger(input.expectedRevision) || input.expectedRevision<0) throw fail(400,'Geçerli inceleme sürümü gerekli.');
const db=openDb();
try {
db.exec('BEGIN IMMEDIATE');
const fileHash=source(db,email,id), previous=latest(db,email,id);
if(input.fileHash!==fileHash || input.expectedRevision!==(previous?.revision || 0)) throw fail(409,'Dosya veya inceleme sürümü değişti. Güncel kaydı yeniden açın.');
const text=input.correctedText.trim();
db.prepare('INSERT INTO document_text_reviews VALUES (?,?,?,?,?,?,?,?)').run(id,email,input.expectedRevision+1,fileHash,hash(text),text,input.note.trim(),new Date().toISOString());
const review=latest(db,email,id);
db.exec('COMMIT');return {documentId:id,fileHash,review,stale:false};
} catch(error) {try {db.exec('ROLLBACK');} catch {} throw error;}
finally {db.close();}
}
/** Resolve a requested review to server-owned text; caller text/approval is never used. */
export function reviewedAnalysisSource(email,reference,tenderKey) {
if(!reference || typeof reference.documentId !== 'string' || !Number.isInteger(reference.revision))
throw fail(400,'Geçerli belge ve inceleme sürümü gerekli.');
const db=openDb();
try {
const fileHash=source(db,email,reference.documentId);
const doc=db.prepare('SELECT ad,ikn FROM belgeler WHERE id=? AND email=?').get(reference.documentId,email);
if(!doc.ikn || doc.ikn!==tenderKey) throw fail(409,'İnceleme bu ihaleye bağlı değil.');
const review=latest(db,email,reference.documentId);
if(!review || review.revision!==reference.revision || review.file_hash!==fileHash)
throw fail(409,'İnceleme veya kaynak dosya değişti; güncel incelemeyi açın.');
return {text:review.corrected_text,fileName:doc.ad,
humanReview:{documentId:reference.documentId,revision:review.revision,fileHash,textHash:review.text_hash,
reviewedBy:email,reviewedAt:review.reviewed_at,note:review.review_note}};
} finally {db.close();}
}