← back to AbramsOS
lib/reviews/model.js
146 lines
'use strict';
/**
* Reviews DB model — reads AbramsOS `purchase` rows, writes `review_draft` rows.
* The content_hash SEAL (set at approval) is what stops stale-approval-replay:
* the executor refuses to fire if the row's current content no longer matches
* the sealed hash. Nothing here posts/sends — see executors.js.
*/
const crypto = require('crypto');
const db = require('../db');
const ids = require('../ids');
const drafts = require('./drafts');
// Pull a human product name out of the purchase's parsed-email extract.
function productFromPurchase(p) {
const raw = p.raw_extract || {};
let s = raw.item || '';
if (!s && raw.subject) {
// 'Ordered: "X..." and 1 more item' | 'Your Amazon.com order of "X...".'
const m = String(raw.subject).match(/["“]([^"”]+)["”]/);
s = m ? m[1] : String(raw.subject).replace(/^(ordered:|your amazon\.com order of)\s*/i, '');
}
s = String(s || '').replace(/\s*(?:,?\s*\.{2,})?\s*and \d+ more items?\.?$/i, '').replace(/^["“]|["”]$/g, '').trim();
return s;
}
function itemShape(p) {
const isAmazon = /amazon/i.test(p.merchant_name || '');
return {
id: p.id, // draft variety is seeded by purchase id — stable across regen
product: productFromPurchase(p),
merchant: p.merchant_name || null,
display_merchant: p.merchant_name || null,
merchant_domain: p.merchant_domain || null,
seller: null, // email receipts don't expose the marketplace seller
order_id: p.order_number || null,
isAmazon,
};
}
// The seal: exactly the content a human approved.
function contentHash(row) {
return crypto.createHash('sha1')
.update([row.target, row.draft_title || '', row.draft_text || '', row.seller_email_to || ''].join('