Files
ihaleist/server/brief-outbox.mjs
T

81 lines
4.2 KiB
JavaScript
Raw 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 { DatabaseSync } from 'node:sqlite';
import { mkdirSync, chmodSync } from 'node:fs';
import path from 'node:path';
import { randomUUID, createHash } from 'node:crypto';
const hash = value => createHash('sha256').update(value).digest('hex');
export function briefWeekKey(now = new Date()) {
const date = new Date(now.getTime() + 3 * 3600000);
date.setUTCDate(date.getUTCDate() - (date.getUTCDay()+6)%7);
return date.toISOString().slice(0,10);
}
/** A claimed message is never blindly replayed after an interrupted provider call. */
export function createBriefOutbox(directory) {
function transaction(work) {
mkdirSync(directory,{recursive:true,mode:0o700});
const file=path.join(directory,'brief.sqlite');
const db = new DatabaseSync(file);
try {
chmodSync(file,0o600);
db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;
CREATE TABLE IF NOT EXISTS brief_batches(id TEXT PRIMARY KEY,week TEXT NOT NULL,email TEXT NOT NULL,manifest TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS brief_messages(batch TEXT NOT NULL,position INTEGER NOT NULL,state TEXT NOT NULL,lease TEXT,lease_until INTEGER,receipt TEXT,error TEXT,PRIMARY KEY(batch,position));`);
db.exec('BEGIN IMMEDIATE');
const result=work(db);
db.exec('COMMIT');
return result;
} catch(error) { try { db.exec('ROLLBACK'); } catch {} throw error; }
finally { db.close(); }
}
return {
prepare(week,email,messages) {
const id=hash(JSON.stringify([week,email])),manifest=JSON.stringify(messages);
return transaction(db=>{
const row=db.prepare('SELECT manifest FROM brief_batches WHERE id=?').get(id);
if(row) {
const changed=row.manifest!==manifest;
if(changed) db.prepare("UPDATE brief_messages SET state='uncertain',error=? WHERE batch=? AND state='pending'")
.run('Haftalık özet içeriği veya alıcı kanalı değişti; yeniden gönderim öncesi inceleme gerekli.',id);
return {id,changed};
}
db.prepare('INSERT INTO brief_batches VALUES (?,?,?,?)').run(id,week,email,manifest);
const insert=db.prepare("INSERT INTO brief_messages(batch,position,state) VALUES (?,?,'pending')");
messages.forEach((_,i)=>insert.run(id,i));
return {id,changed:false};
});
},
claim(batch,position,clock=Date.now()) {
return transaction(db=>{
const row=db.prepare('SELECT * FROM brief_messages WHERE batch=? AND position=?').get(batch,position);
if(!row)throw new Error('Özet teslimat kaydı bulunamadı.');
if(row.state==='accepted')return {state:'accepted'};
if(row.state==='uncertain')return {state:'uncertain'};
if(row.state==='sending') {
if(row.lease_until>clock)return {state:'busy'};
db.prepare("UPDATE brief_messages SET state='uncertain',error=? WHERE batch=? AND position=?").run('Kesintiye uğrayan gönderim; sağlayıcı kaydı incelenmeli.',batch,position);
return {state:'uncertain'};
}
const lease=randomUUID();
db.prepare("UPDATE brief_messages SET state='sending',lease=?,lease_until=? WHERE batch=? AND position=?").run(lease,clock+120000,batch,position);
return {state:'sending',batch,position,lease};
});
},
accept(claim,receipt) {
if(typeof receipt!=='string'||!receipt.trim())throw new Error('Gönderim kabul kaydı gerekli.');
return transaction(db=>{
const result=db.prepare("UPDATE brief_messages SET state='accepted',receipt=?,error=NULL WHERE batch=? AND position=? AND lease=? AND state='sending'")
.run(receipt,claim.batch,claim.position,claim.lease);
if(result.changes!==1)throw new Error('Özet gönderim kaydı değişti.');
});
},
hold(claim,error) {
return transaction(db=>db.prepare("UPDATE brief_messages SET state='uncertain',error=? WHERE batch=? AND position=? AND lease=? AND state='sending'")
.run(String(error?.message || error),claim.batch,claim.position,claim.lease).changes===1);
},
inspect() {
return transaction(db=>db.prepare('SELECT b.week,b.email,m.* FROM brief_messages m JOIN brief_batches b ON b.id=m.batch ORDER BY b.week DESC,b.email,m.position').all());
},
};
}