This commit is contained in:
seb
2026-10-25 22:38:04 +01:00
parent 5fd7f3228c
commit 358e3009b2
17 changed files with 255 additions and 189 deletions

2
API.md
View File

@@ -62,7 +62,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- **ordersummary**: `id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0`
- **package**: `id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt`
- **payment**: `id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f`
- **pic**: `id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin`
- **pic**: `id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), smallthumbw:i, smallthumbh:i, smallthumbbinary:bin, source:txt`
- **posterminal**: `id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt`
- **posterminalcategory**: `id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)`
- **posterminalobituary**: `id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt`

View File

@@ -563,7 +563,7 @@ Company-scoped POS terminals. Auth via selected company (`google_auth.selected_c
`move` moves a category to a new parent. Both `id` (source) and `parentId` (target) are validated via `resolveCategoryAccess` — the target must be a real category of the same authorized company, else `Parent not found`. Moving into itself or into a descendant (cycle) → `Invalid parent`. The moved category gets the next sibling `sort` under the new parent. Returns `{ id, parentId, sort }`. For `POST /api/category/:id/move`.
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`pic.fullbinary`). Thumb always scaled to max 200×200 (`pic.thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `categorypic`. Auth via `resolveCategoryAccess`. Errors: `Category not found`, `Not authorized`.
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`storageDir/fullbinary`). Thumb always scaled to max 200×200 (`storageDir/thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `categorypic`. Auth via `resolveCategoryAccess`. Errors: `Category not found`, `Not authorized`.
`addImage` returns:
@@ -601,7 +601,7 @@ Company-scoped POS terminals. Auth via selected company (`google_auth.selected_c
| `orderImages` | `(googleAuthId: string, sku: string, { fullHashes: string[] })` | `{ fullHashes: string[] }` |
| `delete` | `(googleAuthId: string, sku: string)` | `void` |
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`pic.fullbinary`). Thumb always scaled to max 200×200 (`pic.thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `articlepic`. Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`storageDir/fullbinary`). Thumb always scaled to max 200×200 (`storageDir/thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `articlepic`. Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
`addImage` returns `ImageResult`:

View File

@@ -378,7 +378,7 @@ const SCHEMA_TABLES = [
['ordersummary', 'id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0'],
['package', 'id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt'],
['payment', 'id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f'],
['pic', 'id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin'],
['pic', 'id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), smallthumbw:i, smallthumbh:i, smallthumbbinary:bin, source:txt'],
['posterminal', 'id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt'],
['posterminalcategory', 'id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)'],
['posterminalobituary', 'id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt'],

View File

@@ -4,8 +4,11 @@
* { rows, rowsAffected, insertId }
*/
import { join } from 'node:path';
import { mkdirSync, promises as fsPromises } from 'node:fs';
import sql from 'mssql';
import { logSlowQuery } from '../provider.js';
import { safeWrite, safeReadByHash, safeDeleteByHash } from '../storage.js';
function normalizeRows(result) {
const recordset = result?.recordset ?? [];
@@ -16,13 +19,57 @@ function normalizeRows(result) {
}
export class MssqlExecutor {
/** @param {() => import('mssql').Request} requestFactory */
constructor(requestFactory) {
/**
* @param {() => import('mssql').Request} requestFactory
* @param {string|null} [storageDir=null]
*/
constructor(requestFactory, storageDir = null) {
this.#requestFactory = requestFactory;
this.storageDir = storageDir;
this.dialect = 'mssql';
}
#requestFactory;
async writePicFull(hash, buffer) {
if (!this.storageDir || !hash) return;
await safeWrite(join(this.storageDir, 'fullbinary', hash + '.avif'), buffer);
}
async readPicFull(hash) {
if (!this.storageDir || !hash) return null;
return safeReadByHash(join(this.storageDir, 'fullbinary'), hash, '.avif');
}
async writePicThumb(hash, buffer) {
if (!this.storageDir || !hash) return;
await safeWrite(join(this.storageDir, 'thumbbinary', hash + '.avif'), buffer);
}
async readPicThumb(hash) {
if (!this.storageDir || !hash) return null;
return safeReadByHash(join(this.storageDir, 'thumbbinary'), hash, '.avif');
}
async deletePic(hash) {
if (!this.storageDir || !hash) return;
await safeDeleteByHash(join(this.storageDir, 'fullbinary'), hash);
await safeDeleteByHash(join(this.storageDir, 'thumbbinary'), hash);
}
async purgeFiles() {
if (!this.storageDir) return;
for (const sub of ['fullbinary', 'thumbbinary']) {
const dir = join(this.storageDir, sub);
try {
const files = await fsPromises.readdir(dir);
await Promise.all(files.map((f) => fsPromises.unlink(join(dir, f))));
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
}
/** @returns {Promise<import('../provider.js').QueryResult>} */
async query(sql, params = {}) {
const start = performance.now();
@@ -73,8 +120,9 @@ export class MssqlProvider {
* Either an already-connected mssql ConnectionPool, or a plain mssql
* config object — in which case the pool is created lazily on first use
* (and closed by {@link MssqlProvider#close}).
* @param {{ storageDir?: string|null }} [options]
*/
constructor(poolOrConfig) {
constructor(poolOrConfig, { storageDir = null } = {}) {
const isPool =
poolOrConfig && typeof poolOrConfig.request === 'function';
if (!poolOrConfig || (!isPool && !poolOrConfig.server)) {
@@ -87,6 +135,7 @@ export class MssqlProvider {
// a different object after a lazy connection would leave them with null.
this.pool = isPool ? poolOrConfig : new sql.ConnectionPool(poolOrConfig);
this.#config = isPool ? null : poolOrConfig;
this.storageDir = storageDir || poolOrConfig?.storageDir || null;
this.dialect = 'mssql';
}
@@ -163,12 +212,12 @@ export class MssqlProvider {
}
makeExecutor(requestFactory = () => this.pool.request()) {
return new MssqlExecutor(requestFactory);
return new MssqlExecutor(requestFactory, this.storageDir);
}
async #withPool(fn) {
const pool = await this.ensurePool();
return fn(new MssqlExecutor(() => pool.request()));
return fn(new MssqlExecutor(() => pool.request(), this.storageDir));
}
async query(sql, params = {}) {
@@ -179,6 +228,45 @@ export class MssqlProvider {
return this.#withPool((executor) => executor.exec(procName, options));
}
async writePicFull(hash, buffer) {
if (!this.storageDir || !hash) return;
await safeWrite(join(this.storageDir, 'fullbinary', hash + '.avif'), buffer);
}
async readPicFull(hash) {
if (!this.storageDir || !hash) return null;
return safeReadByHash(join(this.storageDir, 'fullbinary'), hash, '.avif');
}
async writePicThumb(hash, buffer) {
if (!this.storageDir || !hash) return;
await safeWrite(join(this.storageDir, 'thumbbinary', hash + '.avif'), buffer);
}
async readPicThumb(hash) {
if (!this.storageDir || !hash) return null;
return safeReadByHash(join(this.storageDir, 'thumbbinary'), hash, '.avif');
}
async deletePic(hash) {
if (!this.storageDir || !hash) return;
await safeDeleteByHash(join(this.storageDir, 'fullbinary'), hash);
await safeDeleteByHash(join(this.storageDir, 'thumbbinary'), hash);
}
async purgeFiles() {
if (!this.storageDir) return;
for (const sub of ['fullbinary', 'thumbbinary']) {
const dir = join(this.storageDir, sub);
try {
const files = await fsPromises.readdir(dir);
await Promise.all(files.map((f) => fsPromises.unlink(join(dir, f))));
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
}
/**
* Runs `fn(runner)` inside a transaction. Commit on success,
* rollback on error. `runner` has query/exec like the provider.
@@ -197,6 +285,12 @@ export class MssqlProvider {
runner.query = runner;
runner.exec = (proc, opts) => executor.exec(proc, opts);
runner.dialect = 'mssql';
runner.writePicFull = executor.writePicFull.bind(executor);
runner.readPicFull = executor.readPicFull.bind(executor);
runner.writePicThumb = executor.writePicThumb.bind(executor);
runner.readPicThumb = executor.readPicThumb.bind(executor);
runner.deletePic = executor.deletePic.bind(executor);
runner.purgeFiles = executor.purgeFiles.bind(executor);
const result = await fn(runner, tx);
await tx.commit();
return result;
@@ -260,12 +354,21 @@ export class MssqlProvider {
} catch {
// ignore
}
if (this.storageDir) {
for (const sub of ['fullbinary', 'thumbbinary']) {
mkdirSync(join(this.storageDir, sub), { recursive: true });
}
}
}
async purge() {
const pool = await this.ensurePool();
const { buildPurgeSql } = await import('./purgeSql.js');
await pool.request().query(buildPurgeSql());
if (this.storageDir) {
await this.purgeFiles();
}
}
close() {

View File

@@ -191,7 +191,7 @@ SELECT
a.id AS articleId,
p.id AS picId,
p.hash,
p.fullbinary AS buffer
NULL AS buffer
FROM categoryarticle ca
INNER JOIN article a ON a.id = ca.article_id
OUTER APPLY (

View File

@@ -1,14 +1,12 @@
export const CREATE = `
INSERT INTO pic (
w, h, size, hash, thumbw, thumbh, thumbhash,
smallthumbw, smallthumbh, smallthumbbinary,
source, fullbinary, thumbbinary
smallthumbw, smallthumbh, smallthumbbinary, source
)
OUTPUT INSERTED.id
VALUES (
@w, @h, @size, @hash, @thumbw, @thumbh, @thumbhash,
@smallthumbw, @smallthumbh, @smallthumbbinary,
@source, @fullbinary, @thumbbinary
@smallthumbw, @smallthumbh, @smallthumbbinary, @source
)
`;
@@ -60,13 +58,13 @@ WHERE categoryId IS NOT NULL
`;
export const GET_FULL_BY_HASH = `
SELECT w, h, size, fullbinary AS buffer
SELECT w, h, size, hash
FROM pic
WHERE hash = @hash
`;
export const GET_THUMB_BY_HASH = `
SELECT thumbw AS w, thumbh AS h, thumbbinary AS buffer
SELECT thumbw AS w, thumbh AS h, hash
FROM pic
WHERE hash = @hash
`;

View File

@@ -97,10 +97,8 @@ BEGIN
thumbhash NVARCHAR(64) NOT NULL,
smallthumbw INT NOT NULL,
smallthumbh INT NOT NULL,
source NVARCHAR(MAX) NOT NULL,
fullbinary VARBINARY(MAX) NOT NULL,
thumbbinary VARBINARY(MAX) NOT NULL,
smallthumbbinary VARBINARY(MAX) NOT NULL,
source NVARCHAR(MAX) NOT NULL,
CONSTRAINT UQ_pic_hash UNIQUE (hash)
);
END

View File

@@ -51,89 +51,15 @@ function normalizeInsertId(value) {
import { dirname, join } from 'node:path';
import { mkdirSync, mkdtempSync, promises as fsPromises } from 'node:fs';
import { tmpdir } from 'node:os';
import {
extFromMimetype,
safeWrite,
findFileByHash,
safeReadByHash,
safeDeleteByHash,
} from '../storage.js';
export function extFromMimetype(mimetype) {
if (!mimetype) return '';
const type = mimetype.trim().toLowerCase().split(';')[0];
const map = {
'application/pdf': '.pdf',
'text/plain': '.txt',
'text/csv': '.csv',
'text/html': '.html',
'text/markdown': '.md',
'image/avif': '.avif',
'image/png': '.png',
'image/jpeg': '.jpg',
'image/jpg': '.jpg',
'image/webp': '.webp',
'image/gif': '.gif',
'image/svg+xml': '.svg',
'application/json': '.json',
'application/zip': '.zip',
'application/xml': '.xml',
'application/octet-stream': '.bin',
};
if (map[type]) return map[type];
if (type.startsWith('image/')) return '.' + type.slice(6).replace('+xml', '');
if (type.startsWith('text/')) return '.' + type.slice(5);
return '';
}
async function safeWrite(filePath, buffer) {
if (buffer == null) return;
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
await fsPromises.writeFile(filePath, buf);
}
async function findFileByHash(dir, hash, ext = '') {
if (!dir || !hash) return null;
if (ext) {
const candidate = join(dir, hash + ext);
try {
await fsPromises.access(candidate);
return candidate;
} catch {
// fallback
}
}
const candidateNoExt = join(dir, hash);
try {
await fsPromises.access(candidateNoExt);
return candidateNoExt;
} catch {
// fallback
}
try {
const files = await fsPromises.readdir(dir);
const match = files.find((f) => f === hash || f.startsWith(hash + '.'));
if (match) return join(dir, match);
} catch {
// ignore
}
return null;
}
async function safeReadByHash(dir, hash, ext = '') {
const filePath = await findFileByHash(dir, hash, ext);
if (!filePath) return null;
try {
return await fsPromises.readFile(filePath);
} catch (err) {
if (err.code === 'ENOENT') return null;
throw err;
}
}
async function safeDeleteByHash(dir, hash) {
if (!dir || !hash) return;
try {
const files = await fsPromises.readdir(dir);
const matches = files.filter((f) => f === hash || f.startsWith(hash + '.'));
await Promise.all(matches.map((f) => fsPromises.unlink(join(dir, f))));
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
export { extFromMimetype };
export class BetterSqlite3Executor {
/**

89
src/providers/storage.js Normal file
View File

@@ -0,0 +1,89 @@
/**
* Shared filesystem storage utilities for database providers.
*/
import { join } from 'node:path';
import { promises as fsPromises } from 'node:fs';
export async function safeWrite(filePath, buffer) {
if (buffer == null) return;
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
await fsPromises.writeFile(filePath, buf);
}
export async function findFileByHash(dir, hash, ext = '') {
if (!dir || !hash) return null;
if (ext) {
const candidate = join(dir, hash + ext);
try {
await fsPromises.access(candidate);
return candidate;
} catch {
// fallback
}
}
const candidateNoExt = join(dir, hash);
try {
await fsPromises.access(candidateNoExt);
return candidateNoExt;
} catch {
// fallback
}
try {
const files = await fsPromises.readdir(dir);
const match = files.find((f) => f === hash || f.startsWith(hash + '.'));
if (match) return join(dir, match);
} catch {
// ignore
}
return null;
}
export async function safeReadByHash(dir, hash, ext = '') {
const filePath = await findFileByHash(dir, hash, ext);
if (!filePath) return null;
try {
return await fsPromises.readFile(filePath);
} catch (err) {
if (err.code === 'ENOENT') return null;
throw err;
}
}
export async function safeDeleteByHash(dir, hash) {
if (!dir || !hash) return;
try {
const files = await fsPromises.readdir(dir);
const matches = files.filter((f) => f === hash || f.startsWith(hash + '.'));
await Promise.all(matches.map((f) => fsPromises.unlink(join(dir, f))));
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
export function extFromMimetype(mimetype) {
if (!mimetype) return '';
const type = mimetype.trim().toLowerCase().split(';')[0];
const map = {
'application/pdf': '.pdf',
'text/plain': '.txt',
'text/csv': '.csv',
'text/html': '.html',
'text/markdown': '.md',
'image/avif': '.avif',
'image/png': '.png',
'image/jpeg': '.jpg',
'image/jpg': '.jpg',
'image/webp': '.webp',
'image/gif': '.gif',
'image/svg+xml': '.svg',
'application/json': '.json',
'application/zip': '.zip',
'application/xml': '.xml',
'application/octet-stream': '.bin',
};
if (map[type]) return map[type];
if (type.startsWith('image/')) return '.' + type.slice(6).replace('+xml', '');
if (type.startsWith('text/')) return '.' + type.slice(5);
return '';
}

View File

@@ -57,14 +57,12 @@ export default function addImage(provider) {
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);
}
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, {
@@ -79,8 +77,6 @@ export default function addImage(provider) {
smallthumbh: processed.smallThumb.h,
smallthumbbinary: processed.smallThumb.buffer,
source: '',
fullbinary: processed.full.buffer,
thumbbinary: processed.thumb.buffer,
});
picId = picResult.rows[0]?.id ?? picResult.insertId;

View File

@@ -30,26 +30,14 @@ export default function deleteImage(provider) {
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;
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');
}
const thumbBuffer = await (runner.readPicThumb ? runner.readPicThumb(fullHash) : provider.readPicThumb?.(fullHash));
let skelehash = null;
if (thumbBuffer) {
@@ -84,12 +72,10 @@ export default function deleteImage(provider) {
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);
}
if (runner.deletePic) {
await runner.deletePic(fullHash);
} else if (provider?.deletePic) {
await provider.deletePic(fullHash);
}
await recordImageDeleteHistory(runner, {

View File

@@ -51,14 +51,12 @@ export default function addImage(provider) {
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);
}
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, {
@@ -73,8 +71,6 @@ export default function addImage(provider) {
smallthumbh: processed.smallThumb.h,
smallthumbbinary: processed.smallThumb.buffer,
source: '',
fullbinary: processed.full.buffer,
thumbbinary: processed.thumb.buffer,
});
picId = picResult.rows[0]?.id ?? picResult.insertId;

View File

@@ -51,14 +51,12 @@ export default function addImage(provider) {
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);
}
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, {
@@ -73,8 +71,6 @@ export default function addImage(provider) {
smallthumbh: processed.smallThumb.h,
smallthumbbinary: processed.smallThumb.buffer,
source: '',
fullbinary: processed.full.buffer,
thumbbinary: processed.thumb.buffer,
});
picId = picResult.rows[0]?.id ?? picResult.insertId;

View File

@@ -56,14 +56,12 @@ export default function addImage(provider) {
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);
}
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, {
@@ -78,8 +76,6 @@ export default function addImage(provider) {
smallthumbh: processed.smallThumb.h,
smallthumbbinary: processed.smallThumb.buffer,
source: '',
fullbinary: processed.full.buffer,
thumbbinary: processed.thumb.buffer,
});
picId = picResult.rows[0]?.id ?? picResult.insertId;

View File

@@ -123,17 +123,9 @@ export default function generateImage(provider) {
selected.map(async (item) => {
let buf = item.buffer;
if (!buf || !buf.length) {
if (isSqlite) {
buf = runner.readPicFull ? await runner.readPicFull(item.hash) : await provider?.readPicFull?.(item.hash);
if (!buf) {
buf = runner.readPicThumb ? await runner.readPicThumb(item.hash) : await provider?.readPicThumb?.(item.hash);
}
} else {
const picRes = await runner.query(
'SELECT fullbinary, thumbbinary FROM pic WHERE hash = @hash',
{ hash: item.hash },
);
buf = picRes.rows[0]?.fullbinary ?? picRes.rows[0]?.thumbbinary;
buf = runner.readPicFull ? await runner.readPicFull(item.hash) : await provider?.readPicFull?.(item.hash);
if (!buf) {
buf = runner.readPicThumb ? await runner.readPicThumb(item.hash) : await provider?.readPicThumb?.(item.hash);
}
}
return { ...item, buffer: buf };

View File

@@ -31,12 +31,7 @@ export default function getImageByHash(provider) {
throw new Error('Image not found');
}
let buffer;
if (provider?.dialect === 'sqlite3') {
buffer = await (runner.readPicFull ? runner.readPicFull(fullHash) : provider.readPicFull?.(fullHash));
} else {
buffer = row.buffer ?? row.fullbinary;
}
const buffer = await (runner.readPicFull ? runner.readPicFull(fullHash) : provider.readPicFull?.(fullHash));
if (!buffer) {
throw new Error('Image not found');

View File

@@ -31,12 +31,7 @@ export default function getThumbByHash(provider) {
throw new Error('Image not found');
}
let buffer;
if (provider?.dialect === 'sqlite3') {
buffer = await (runner.readPicThumb ? runner.readPicThumb(fullHash) : provider.readPicThumb?.(fullHash));
} else {
buffer = row.buffer ?? row.thumbbinary;
}
const buffer = await (runner.readPicThumb ? runner.readPicThumb(fullHash) : provider.readPicThumb?.(fullHash));
if (!buffer) {
throw new Error('Image not found');