import { ADD as MSSQL_ADD_CATEGORYPIC, GET_NEXT_SORT as MSSQL_GET_NEXT_SORT, } from '../../providers/mssql/queries/categorypic.js'; import { ADD as SQLITE_ADD_CATEGORYPIC, GET_NEXT_SORT as SQLITE_GET_NEXT_SORT, } from '../../providers/sqlite3/queries/categorypic.js'; import { CREATE as MSSQL_CREATE_PIC } from '../../providers/mssql/queries/pic.js'; import { CREATE as SQLITE_CREATE_PIC } from '../../providers/sqlite3/queries/pic.js'; import processAvifImage from '../../util/processAvifImage.js'; import newFullHash from '../../util/newFullHash.js'; import resolveCategoryAccess from './resolveCategoryAccess.js'; import makeRunner from '../runner.js'; export default function addImage(provider) { const access = resolveCategoryAccess(provider); const isSqlite = provider?.dialect === 'sqlite3'; const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT; const createPicSql = isSqlite ? SQLITE_CREATE_PIC : MSSQL_CREATE_PIC; const addCategoryPicSql = isSqlite ? SQLITE_ADD_CATEGORYPIC : MSSQL_ADD_CATEGORYPIC; const touchSql = isSqlite ? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId" : 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId'; /** * Adds an image to a category, processing full, thumb, and 32px-edge small-thumb AVIF variants. * * @param {number} googleAuthId - The id of the Google auth user. * @param {number} categoryId - The id of the category. * @param {Buffer} image - The source image buffer. * @param {object} [tx] - Optional transaction to run within. * @returns {Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, smallThumbW: number, smallThumbH: number}>} The added image info. * * @example * const result = await store.category.addImage(42, 5, imageBuffer); * // => { id, fullHash, thumbW, thumbH, thumbHash } */ return async (googleAuthId, categoryId, image, tx) => { const processed = await processAvifImage(image); const fullHash = newFullHash(processed.full.buffer); const runAdd = async (runner) => { await access(googleAuthId, categoryId, runner); const sortResult = await runner.query(getSortSql, { categoryId }); const sort = sortResult.rows[0].sort; let picId; const existingPic = await runner.query( 'SELECT id FROM pic WHERE hash = @fullHash', { fullHash }, ); if (existingPic.rows[0]) { picId = existingPic.rows[0].id; } else { if (isSqlite) { if (runner.writePicFull) { await runner.writePicFull(fullHash, processed.full.buffer); await runner.writePicThumb(fullHash, processed.thumb.buffer); } else if (provider?.writePicFull) { await provider.writePicFull(fullHash, processed.full.buffer); await provider.writePicThumb(fullHash, processed.thumb.buffer); } } const picResult = await runner.query(createPicSql, { w: processed.full.w, h: processed.full.h, size: processed.full.size, hash: fullHash, thumbw: processed.thumb.w, thumbh: processed.thumb.h, thumbhash: processed.thumb.hash, smallthumbw: processed.smallThumb.w, smallthumbh: processed.smallThumb.h, smallthumbbinary: processed.smallThumb.buffer, source: '', fullbinary: processed.full.buffer, thumbbinary: processed.thumb.buffer, }); picId = picResult.rows[0]?.id ?? picResult.insertId; } const existingCategoryPic = await runner.query( 'SELECT id, sort FROM categorypic WHERE category_id = @categoryId AND pic_id = @picId', { categoryId, picId }, ); if (!existingCategoryPic.rows[0]) { await runner.query(addCategoryPicSql, { categoryId, picId, sort, }); } await runner.query(touchSql, { categoryId }); return { id: categoryId, fullHash, thumbW: processed.thumb.w, thumbH: processed.thumb.h, thumbHash: processed.thumb.hash, smallThumbW: processed.smallThumb.w, smallThumbH: processed.smallThumb.h, }; }; if (tx) { return runAdd(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runAdd); } return runAdd(makeRunner(provider)); }; }