← back to Rentv Adintel
db/seed/flyers.js
172 lines
'use strict';
/**
* Seed creative_assets for RENTV flyer files.
*
* Per §22: check for the two flyer files. They are expected to be absent
* (confirmed absent at /Users/macstudio3/Downloads/ and /mnt/data/).
* When absent: insert GENERATED_PLACEHOLDER creative_assets rows and an
* audit_log entry requesting admin upload.
*
* When present: copy to public/seed/rentv/, compute sha256 + image dimensions,
* label AUTHORIZED_RENTV_ASSET.
*
* Idempotent: checks by file_name before inserting.
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const FLYER_SEARCH_PATHS = [
'/mnt/data',
'/Users/macstudio3/Downloads',
];
const FLYERS = [
{
file_name: 'Property Spotlight Flyer REV April 2025.jpg',
mime_type: 'image/jpeg',
alt_text:
'RENTV Property Spotlight Flyer — revised April 2025. Shows RENTV property spotlight eblast product details and pricing.',
product_key: 'property_spotlight_eblast',
notes: 'April 2025 version of the RENTV Property Spotlight product flyer.',
},
{
file_name: 'Corp Flyer Apr 2026 V3.jpg',
mime_type: 'image/jpeg',
alt_text:
'RENTV Corporate Flyer — April 2026 Version 3. Shows RENTV advertising product suite, audience figures, and updated rates.',
product_key: 'website_banner',
notes: 'April 2026 v3 RENTV corporate media kit flyer.',
},
];
function findFlyer(fileName) {
for (const dir of FLYER_SEARCH_PATHS) {
const full = path.join(dir, fileName);
if (fs.existsSync(full)) return full;
}
return null;
}
function sha256File(filePath) {
const data = fs.readFileSync(filePath);
return crypto.createHash('sha256').update(data).digest('hex');
}
/** Parse JPEG dimensions from header bytes (minimal, no external deps). */
function jpegDimensions(filePath) {
try {
const fd = fs.openSync(filePath, 'r');
const buf = Buffer.alloc(65536);
fs.readSync(fd, buf, 0, 65536, 0);
fs.closeSync(fd);
// Scan for SOF0/SOF1/SOF2 markers (0xFFC0, 0xFFC1, 0xFFC2)
for (let i = 0; i < buf.length - 8; i++) {
if (buf[i] === 0xff && (buf[i + 1] === 0xc0 || buf[i + 1] === 0xc1 || buf[i + 1] === 0xc2)) {
const height = buf.readUInt16BE(i + 5);
const width = buf.readUInt16BE(i + 7);
return { width, height };
}
}
} catch (_) {
// ignore
}
return { width: null, height: null };
}
async function seedFlyers(client) {
let inserted = 0;
let skipped = 0;
let placeholders = 0;
// Ensure public/seed/rentv/ directory exists (relative to project root)
const PROJECT_ROOT = path.resolve(__dirname, '../..');
const publicSeedDir = path.join(PROJECT_ROOT, 'public', 'seed', 'rentv');
if (!fs.existsSync(publicSeedDir)) {
fs.mkdirSync(publicSeedDir, { recursive: true });
}
for (const flyer of FLYERS) {
// Idempotency check
const existing = await client.query(
`SELECT id FROM creative_assets WHERE file_name=$1 LIMIT 1`,
[flyer.file_name]
);
if (existing.rows.length > 0) {
skipped++;
continue;
}
const foundPath = findFlyer(flyer.file_name);
if (foundPath) {
// File present — compute checksum and dims, copy to public/seed/rentv/
const destPath = path.join(publicSeedDir, flyer.file_name);
fs.copyFileSync(foundPath, destPath);
const checksum = sha256File(foundPath);
const { width, height } = jpegDimensions(foundPath);
const objectKey = `seed/rentv/${flyer.file_name}`;
await client.query(
`INSERT INTO creative_assets
(file_name, mime_type, width, height, checksum, object_key,
capture_method, rights_status, captured_at, alt_text)
VALUES ($1,$2,$3,$4,$5,$6,'MANUAL_UPLOAD','EXPORT_ALLOWED',now(),$7)`,
[flyer.file_name, flyer.mime_type, width, height, checksum, objectKey, flyer.alt_text]
);
const alFoundExist = await client.query(
`SELECT id FROM audit_logs WHERE action='SEED_ASSET_FOUND' AND detail->>'file_name'=$1 LIMIT 1`,
[flyer.file_name]
);
if (alFoundExist.rows.length === 0) {
await client.query(
`INSERT INTO audit_logs (action, entity_table, actor, detail)
VALUES ('SEED_ASSET_FOUND','creative_assets','seed',$1::jsonb)`,
[JSON.stringify({ file_name: flyer.file_name, checksum, width, height, object_key: objectKey })]
);
}
inserted++;
} else {
// File absent — create GENERATED_PLACEHOLDER
await client.query(
`INSERT INTO creative_assets
(file_name, mime_type, capture_method, rights_status, alt_text)
VALUES ($1,$2,'GENERATED_PLACEHOLDER','INTERNAL_EVIDENCE_ONLY',$3)`,
[flyer.file_name, flyer.mime_type, flyer.alt_text]
);
placeholders++;
const alMissExist = await client.query(
`SELECT id FROM audit_logs WHERE action='SEED_ASSET_MISSING' AND detail->>'file_name'=$1 LIMIT 1`,
[flyer.file_name]
);
if (alMissExist.rows.length === 0) {
await client.query(
`INSERT INTO audit_logs (action, entity_table, actor, detail)
VALUES ('SEED_ASSET_MISSING','creative_assets','seed',$1::jsonb)`,
[
JSON.stringify({
file_name: flyer.file_name,
note:
'Flyer asset not found at expected paths (' +
FLYER_SEARCH_PATHS.join(', ') +
'). Admin upload card required. ' +
flyer.notes,
admin_action_required: true,
searched_paths: FLYER_SEARCH_PATHS,
product_key: flyer.product_key,
}),
]
);
}
}
}
return { inserted, skipped, placeholders };
}
module.exports = { seedFlyers };