import sharp from 'sharp'; import { encodeRGBA } from 'skelehash-wasm'; import { DELETE_BY_ARTICLE_AND_HASH as MSSQL_QUERY } from '../../providers/mssql/queries/pic.js'; import { DELETE_BY_ARTICLE_AND_HASH as SQLITE_QUERY } from '../../providers/sqlite3/queries/pic.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import { recordImageDeleteHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function deleteImage(provider) { const resolve = resolveArticleBySku(provider); const sql = provider?.dialect === 'sqlite3' ? SQLITE_QUERY : MSSQL_QUERY; /** * Deletes an image from an article by its SKU and full image hash. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {string} fullHash - The full image hash to delete. * @param {object} [tx] - Optional transaction. * @returns {Promise} * @throws {Error} If the image is not found. * * @example * await store.article.deleteImage(42, 'SKU-1', 'abc123'); */ return async (googleAuthId, sku, fullHash, tx) => { const runDelete = async (runner) => { const { articleId, companyuserId } = await resolve(googleAuthId, sku, runner); let thumbBuffer; if (provider?.dialect === 'sqlite3') { const exists = await runner.query( 'SELECT ap.id FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id WHERE ap.article_id = @articleId AND p.hash = @fullHash', { articleId, fullHash }, ); if (!exists.rows[0]) { throw new Error('Image not found'); } thumbBuffer = await (runner.readPicThumb ? runner.readPicThumb(fullHash) : provider.readPicThumb?.(fullHash)); } else { const picResult = await runner.query( 'SELECT p.thumbbinary FROM pic p INNER JOIN articlepic ap ON ap.pic_id = p.id WHERE ap.article_id = @articleId AND p.hash = @fullHash', { articleId, fullHash }, ); if (!picResult.rows[0]) { throw new Error('Image not found'); } thumbBuffer = picResult.rows[0]?.thumbbinary; } let skelehash = null; if (thumbBuffer) { const { data, info } = await sharp(thumbBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true }); const res = await encodeRGBA(info.width, info.height, data); skelehash = res?.base62; } if (!skelehash) { const historyRows = await runner.query( 'SELECT meta FROM articlehistory WHERE article_id = @articleId AND meta LIKE @pattern', { articleId, pattern: `%"${fullHash}"%` }, ); for (const row of historyRows.rows || []) { try { const m = typeof row.meta === 'string' ? JSON.parse(row.meta) : row.meta; if (m?.fullHash === fullHash && m?.skelehash) { skelehash = m.skelehash; break; } } catch {} } } if (!skelehash) { throw new Error(`Failed to resolve skelehash for image ${fullHash}`); } const result = await runner.query(sql, { articleId, fullHash }); if (!result.rowsAffected) { throw new Error('Image not found'); } if (provider?.dialect === 'sqlite3') { if (runner.deletePic) { await runner.deletePic(fullHash); } else if (provider?.deletePic) { await provider.deletePic(fullHash); } } await recordImageDeleteHistory(runner, { articleId, companyuserId, fullHash, skelehash, }); }; if (tx) { return runDelete(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runDelete); } return runDelete(makeRunner(provider)); }; }