62 lines
3.7 KiB
JavaScript
62 lines
3.7 KiB
JavaScript
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();}
|
||
}
|