Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 5x 5x 5x 5x 5x 5x 5x 11x 3x 10x 10x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 8x 8x 6x 5x 5x 4x 4x 3x 3x 2x 2x 5x 5x | const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { getAxios } = require('../axiosLoader');
const MEDIA_ROOT = path.join(__dirname, '..', '..', 'public', 'projects_media');
const MAX = 2 * 1024 * 1024; // 2 MB
const DEBUG_FETCH = process.env.DEBUG_FETCH === '1' || process.env.DEBUG_FETCH === 'true';
function ensureDir(dir) {
try { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
}
function md5(text) {
try { return crypto.createHash('md5').update(String(text || '')).digest('hex'); } catch (e) { return null; }
}
/**
* Downloads a remote image into `public/projects_media/<repoName>/` with a
* deterministic filename (sanitized basename + md5(url) suffix).
* Skips files already on disk, enforces a 2 MB cap, and rejects non-image Content-Types.
*
* @param {string} repoName - GitHub repo name; used as the media subfolder
* @param {string} url - Absolute URL of the image to download
* @param {object} [opts] - Reserved for future overrides (currently unused)
* @returns {Promise<string|null>} Filename relative to the repo media dir, or null on failure
*/
async function downloadIfNeeded(repoName, url, opts = {}) {
try {
if (!url) return null;
const axios = getAxios();
const u = String(url).split('?')[0];
let ext = path.extname(u).toLowerCase();
if (!ext || ext.length > 6) ext = '.png';
const safeBase = path.basename(u).replace(/[^a-z0-9._-]/gi, '-').replace(/^-+|-+$/g, '');
const hash = crypto.createHash('md5').update(String(url)).digest('hex').slice(0, 8);
const fn = `${safeBase}-${hash}${ext}`;
const destDir = path.join(MEDIA_ROOT, repoName);
ensureDir(destDir);
const outPath = path.join(destDir, fn);
if (fs.existsSync(outPath)) return fn; // already present
Iif (!axios) return null;
const res = await axios.get(url, { responseType: 'arraybuffer', maxRedirects: 5, timeout: 15000 });
if (!res || !res.data) return null;
const buf = Buffer.from(res.data);
if (buf.length > MAX) return null;
const ct = (res.headers && (res.headers['content-type'] || res.headers['Content-Type'])) || '';
if (!/image\//i.test(ct) && !/\.(png|jpe?g|gif|svg)$/i.test(ext)) return null;
fs.writeFileSync(outPath, buf);
return fn;
} catch (e) {
Iif (DEBUG_FETCH) console.log('mediaDownloader.downloadIfNeeded failed', url, e && e.message);
return null;
}
}
const persistence = require('./persistence');
module.exports = { ensureDir, md5, downloadIfNeeded, persistMetaForNode: persistence.persistMetaForNode };
|