u
This commit is contained in:
2
API.md
2
API.md
@@ -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`
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
`;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
89
src/providers/storage.js
Normal 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 '';
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user