u
This commit is contained in:
@@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||||
|
|
||||||
|
## `attributevalue.addImage`
|
||||||
|
|
||||||
|
*Source: `src/store/attributevalue/addImage.js`*
|
||||||
|
|
||||||
|
Adds an image to an attribute value, processing full, thumb, and 32px-edge small-thumb AVIF variants.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||||
|
| `attributeValueId` | `number` | The id of the attribute value. |
|
||||||
|
| `image` | `Buffer` | The source image buffer. |
|
||||||
|
| `[tx]` | `object` | Optional transaction to run within. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, smallThumbW: number, smallThumbH: number, pics: Array<object>}>`
|
||||||
|
|
||||||
## `attributevalue.create`
|
## `attributevalue.create`
|
||||||
|
|
||||||
*Source: `src/store/attributevalue/create.js`*
|
*Source: `src/store/attributevalue/create.js`*
|
||||||
@@ -49,6 +68,28 @@ Deletes an attribute value.
|
|||||||
|
|
||||||
`Error` — If attribute value not found or in use.
|
`Error` — If attribute value not found or in use.
|
||||||
|
|
||||||
|
## `attributevalue.deleteImage`
|
||||||
|
|
||||||
|
*Source: `src/store/attributevalue/deleteImage.js`*
|
||||||
|
|
||||||
|
Deletes the image of an attribute value.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||||
|
| `attributeValueId` | `number` | The id of the attribute value. |
|
||||||
|
| `[tx]` | `object` | Optional transaction. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null, pics: Array}>`
|
||||||
|
|
||||||
|
**Throws**
|
||||||
|
|
||||||
|
`Error` — If attribute value not found or image not found.
|
||||||
|
|
||||||
## `attributevalue.listByAttributeId`
|
## `attributevalue.listByAttributeId`
|
||||||
|
|
||||||
*Source: `src/store/attributevalue/listByAttributeId.js`*
|
*Source: `src/store/attributevalue/listByAttributeId.js`*
|
||||||
|
|||||||
3
API.md
3
API.md
@@ -20,6 +20,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- **articletext**: `id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)`
|
- **articletext**: `id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)`
|
||||||
- **attributename**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
- **attributename**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
||||||
- **attributevalue**: `id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)`
|
- **attributevalue**: `id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)`
|
||||||
|
- **attributevaluepic**: `id PK, attributevalue_id->attributevalue, pic_id->pic, sort:i=0, UQ(attributevalue_id, pic_id), UQ(attributevalue_id, sort)`
|
||||||
- **brand**: `id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)`
|
- **brand**: `id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)`
|
||||||
- **brandpic**: `id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)`
|
- **brandpic**: `id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)`
|
||||||
- **category**: `id PK, parent_id?->category, sort:i=0`
|
- **category**: `id PK, parent_id?->category, sort:i=0`
|
||||||
@@ -264,8 +265,10 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `order(googleAuthId: number, input: {attributenameIds: Array<number>}, tx?: object): Promise<{attributenameIds: Array<number>}>`
|
- `order(googleAuthId: number, input: {attributenameIds: Array<number>}, tx?: object): Promise<{attributenameIds: Array<number>}>`
|
||||||
- `update(googleAuthId: number, id: number, input: {name?: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean, values: Array}>`
|
- `update(googleAuthId: number, id: number, input: {name?: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean, values: Array}>`
|
||||||
- [attributevalue](API.attributevalue.md)
|
- [attributevalue](API.attributevalue.md)
|
||||||
|
- `addImage(googleAuthId: number, attributeValueId: number, image: Buffer, tx?: object): Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, smallThumbW: number, smallThumbH: number, pics: Array<object>}>`
|
||||||
- `create(googleAuthId: number, attributeId: number, input: {value: string, sort?: number}, tx?: object): Promise<{id: number, attributeId: number, value: string, sort: number}>`
|
- `create(googleAuthId: number, attributeId: number, input: {value: string, sort?: number}, tx?: object): Promise<{id: number, attributeId: number, value: string, sort: number}>`
|
||||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||||
|
- `deleteImage(googleAuthId: number, attributeValueId: number, tx?: object): Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null, pics: Array}>`
|
||||||
- `listByAttributeId(googleAuthId: number, attributeId: number, tx?: object): Promise<Array<{id: number, attributeId: number, value: string, sort: number}>>`
|
- `listByAttributeId(googleAuthId: number, attributeId: number, tx?: object): Promise<Array<{id: number, attributeId: number, value: string, sort: number}>>`
|
||||||
- `order(googleAuthId: number, attributeId: number, input: {attributevalueIds: Array<number>}, tx?: object): Promise<{attributevalueIds: Array<number>}>`
|
- `order(googleAuthId: number, attributeId: number, input: {attributevalueIds: Array<number>}, tx?: object): Promise<{attributevalueIds: Array<number>}>`
|
||||||
- `update(googleAuthId: number, id: number, input: {value?: string, sort?: number}, tx?: object): Promise<{id: number, attributeId: number, value: string, sort: number}>`
|
- `update(googleAuthId: number, id: number, input: {value?: string, sort?: number}, tx?: object): Promise<{id: number, attributeId: number, value: string, sort: number}>`
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ const SCHEMA_TABLES = [
|
|||||||
['articletext', 'id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)'],
|
['articletext', 'id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)'],
|
||||||
['attributename', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
['attributename', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
||||||
['attributevalue', 'id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)'],
|
['attributevalue', 'id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)'],
|
||||||
|
['attributevaluepic', 'id PK, attributevalue_id->attributevalue, pic_id->pic, sort:i=0, UQ(attributevalue_id, pic_id), UQ(attributevalue_id, sort)'],
|
||||||
['brand', 'id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)'],
|
['brand', 'id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)'],
|
||||||
['brandpic', 'id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)'],
|
['brandpic', 'id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)'],
|
||||||
['category', 'id PK, parent_id?->category, sort:i=0'],
|
['category', 'id PK, parent_id?->category, sort:i=0'],
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ const CLUSTERS = [
|
|||||||
{
|
{
|
||||||
id: 'media',
|
id: 'media',
|
||||||
label: 'Media',
|
label: 'Media',
|
||||||
tables: ['pic', 'articlepic', 'categorypic', 'brandpic', 'file'],
|
tables: ['pic', 'articlepic', 'categorypic', 'brandpic', 'attributevaluepic', 'file'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'manifests',
|
id: 'manifests',
|
||||||
|
|||||||
225
schema-graph.dot
225
schema-graph.dot
File diff suppressed because one or more lines are too long
5054
schema-graph.svg
5054
schema-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 320 KiB |
115
scripts/test-attributevalue-pic.js
Normal file
115
scripts/test-attributevalue-pic.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import LibDashStore from '../src/index.js';
|
||||||
|
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
console.log('--- Testing attributevaluepic & attributevalue image operations ---');
|
||||||
|
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-attrval-pic-'));
|
||||||
|
const dbFile = join(tempDir, 'test.db');
|
||||||
|
const storageDir = join(tempDir, 'storage');
|
||||||
|
|
||||||
|
const provider = new BetterSqlite3Provider({
|
||||||
|
filename: dbFile,
|
||||||
|
storageDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.initialize();
|
||||||
|
const store = await new LibDashStore(provider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const googleAuthId = await store.google_auth.add('tester@example.com');
|
||||||
|
const company = await store.company.create({
|
||||||
|
name: 'Test Corp',
|
||||||
|
iso639: 'de',
|
||||||
|
taxrate: 19,
|
||||||
|
reducedtaxrate: 7,
|
||||||
|
googleAuthId,
|
||||||
|
});
|
||||||
|
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||||
|
|
||||||
|
const attr = await store.attributename.create(googleAuthId, {
|
||||||
|
name: 'Color',
|
||||||
|
exclusive: true,
|
||||||
|
});
|
||||||
|
const val = await store.attributevalue.create(googleAuthId, attr.id, {
|
||||||
|
value: 'Midnight Blue',
|
||||||
|
});
|
||||||
|
assert.ok(val.id > 0);
|
||||||
|
|
||||||
|
// Create a 100x100 test image
|
||||||
|
const imageBuffer = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 25, g: 25, b: 112, alpha: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.avif()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
console.log('1. Adding image to attribute value...');
|
||||||
|
const addResult = await store.attributevalue.addImage(googleAuthId, val.id, imageBuffer);
|
||||||
|
assert.equal(addResult.id, val.id);
|
||||||
|
assert.ok(addResult.fullHash);
|
||||||
|
assert.ok(addResult.thumbW > 0);
|
||||||
|
assert.ok(addResult.thumbH > 0);
|
||||||
|
assert.ok(addResult.thumbHash);
|
||||||
|
assert.equal(addResult.pics.length, 1);
|
||||||
|
assert.equal(addResult.pics[0].fullHash, addResult.fullHash);
|
||||||
|
|
||||||
|
// Verify row in attributevaluepic
|
||||||
|
const rows = await provider.query('SELECT * FROM attributevaluepic WHERE attributevalue_id = @id', {
|
||||||
|
id: val.id,
|
||||||
|
});
|
||||||
|
assert.equal(rows.rows.length, 1);
|
||||||
|
assert.equal(rows.rows[0].attributevalue_id, val.id);
|
||||||
|
|
||||||
|
// Add again to test replacement
|
||||||
|
console.log('2. Replacing image on attribute value...');
|
||||||
|
const replaceResult = await store.attributevalue.addImage(googleAuthId, val.id, imageBuffer);
|
||||||
|
assert.equal(replaceResult.id, val.id);
|
||||||
|
const rowsAfterReplace = await provider.query('SELECT * FROM attributevaluepic WHERE attributevalue_id = @id', {
|
||||||
|
id: val.id,
|
||||||
|
});
|
||||||
|
assert.equal(rowsAfterReplace.rows.length, 1);
|
||||||
|
|
||||||
|
// Delete image
|
||||||
|
console.log('3. Deleting image from attribute value...');
|
||||||
|
const deleteResult = await store.attributevalue.deleteImage(googleAuthId, val.id);
|
||||||
|
assert.equal(deleteResult.id, val.id);
|
||||||
|
assert.equal(deleteResult.thumbW, null);
|
||||||
|
assert.equal(deleteResult.thumbH, null);
|
||||||
|
assert.equal(deleteResult.thumbHash, null);
|
||||||
|
assert.deepEqual(deleteResult.pics, []);
|
||||||
|
|
||||||
|
const rowsAfterDelete = await provider.query('SELECT * FROM attributevaluepic WHERE attributevalue_id = @id', {
|
||||||
|
id: val.id,
|
||||||
|
});
|
||||||
|
assert.equal(rowsAfterDelete.rows.length, 0);
|
||||||
|
|
||||||
|
// Deleting again should fail with Image not found
|
||||||
|
await assert.rejects(
|
||||||
|
async () => {
|
||||||
|
await store.attributevalue.deleteImage(googleAuthId, val.id);
|
||||||
|
},
|
||||||
|
/Image not found/
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('All attributevaluepic tests passed successfully!');
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch((err) => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -212,6 +212,8 @@ import attributevalueCreate from './store/attributevalue/create.js';
|
|||||||
import attributevalueUpdate from './store/attributevalue/update.js';
|
import attributevalueUpdate from './store/attributevalue/update.js';
|
||||||
import attributevalueOrder from './store/attributevalue/order.js';
|
import attributevalueOrder from './store/attributevalue/order.js';
|
||||||
import attributevalueDelete from './store/attributevalue/delete.js';
|
import attributevalueDelete from './store/attributevalue/delete.js';
|
||||||
|
import attributevalueAddImage from './store/attributevalue/addImage.js';
|
||||||
|
import attributevalueDeleteImage from './store/attributevalue/deleteImage.js';
|
||||||
import articleattributevalueListByArticleSku from './store/articleattributevalue/listByArticleSku.js';
|
import articleattributevalueListByArticleSku from './store/articleattributevalue/listByArticleSku.js';
|
||||||
import articleattributevalueSetByArticleSku from './store/articleattributevalue/setByArticleSku.js';
|
import articleattributevalueSetByArticleSku from './store/articleattributevalue/setByArticleSku.js';
|
||||||
import categorypropertyListByCategoryId from './store/categoryproperty/listByCategoryId.js';
|
import categorypropertyListByCategoryId from './store/categoryproperty/listByCategoryId.js';
|
||||||
@@ -574,6 +576,8 @@ export default class LibDashStore {
|
|||||||
update: attributevalueUpdate(provider),
|
update: attributevalueUpdate(provider),
|
||||||
order: attributevalueOrder(provider),
|
order: attributevalueOrder(provider),
|
||||||
delete: attributevalueDelete(provider),
|
delete: attributevalueDelete(provider),
|
||||||
|
addImage: attributevalueAddImage(provider),
|
||||||
|
deleteImage: attributevalueDeleteImage(provider),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.articleattributevalue = {
|
this.articleattributevalue = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const TABLES = [
|
|||||||
'account',
|
'account',
|
||||||
'articleattributevalue',
|
'articleattributevalue',
|
||||||
'articleproperty',
|
'articleproperty',
|
||||||
|
'attributevaluepic',
|
||||||
'attributevalue',
|
'attributevalue',
|
||||||
'attributename',
|
'attributename',
|
||||||
'propertyname',
|
'propertyname',
|
||||||
|
|||||||
23
src/providers/mssql/queries/attributevaluepic.js
Normal file
23
src/providers/mssql/queries/attributevaluepic.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export const GET_NEXT_SORT = `
|
||||||
|
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
|
||||||
|
FROM attributevaluepic
|
||||||
|
WHERE attributevalue_id = @attributeValueId
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ADD = `
|
||||||
|
INSERT INTO attributevaluepic (attributevalue_id, pic_id, sort)
|
||||||
|
VALUES (@attributeValueId, @picId, @sort)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DELETE_BY_ATTRIBUTE_VALUE = `
|
||||||
|
DELETE FROM attributevaluepic
|
||||||
|
WHERE attributevalue_id = @attributeValueId
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GET_FIRST_PIC_BY_ATTRIBUTE_VALUE = `
|
||||||
|
SELECT TOP 1 p.id AS picId, p.thumbw AS thumbW, p.thumbh AS thumbH, p.thumbhash AS thumbHash
|
||||||
|
FROM attributevaluepic avp
|
||||||
|
INNER JOIN pic p ON p.id = avp.pic_id
|
||||||
|
WHERE avp.attributevalue_id = @attributeValueId
|
||||||
|
ORDER BY avp.sort ASC, avp.id ASC
|
||||||
|
`;
|
||||||
@@ -45,6 +45,16 @@ FROM (
|
|||||||
INNER JOIN brand b ON b.id = bp.brand_id
|
INNER JOIN brand b ON b.id = bp.brand_id
|
||||||
INNER JOIN company c ON c.id = b.company_id
|
INNER JOIN company c ON c.id = b.company_id
|
||||||
WHERE p.hash = @hash
|
WHERE p.hash = @hash
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT c.root_category_id AS categoryId
|
||||||
|
FROM pic p
|
||||||
|
INNER JOIN attributevaluepic avp ON avp.pic_id = p.id
|
||||||
|
INNER JOIN attributevalue av ON av.id = avp.attributevalue_id
|
||||||
|
INNER JOIN attributename an ON an.id = av.attribute_id
|
||||||
|
INNER JOIN company c ON c.id = an.company_id
|
||||||
|
WHERE p.hash = @hash
|
||||||
) AS combined
|
) AS combined
|
||||||
WHERE categoryId IS NOT NULL
|
WHERE categoryId IS NOT NULL
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -463,6 +463,22 @@ BEGIN
|
|||||||
END
|
END
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const INIT_ATTRIBUTEVALUEPIC = `
|
||||||
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'attributevaluepic')
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE attributevaluepic (
|
||||||
|
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||||
|
attributevalue_id INT NOT NULL,
|
||||||
|
pic_id INT NOT NULL,
|
||||||
|
sort INT NOT NULL CONSTRAINT DF_attributevaluepic_sort DEFAULT 0,
|
||||||
|
CONSTRAINT FK_attributevaluepic_attributevalue FOREIGN KEY (attributevalue_id) REFERENCES attributevalue(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT FK_attributevaluepic_pic FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT UQ_attributevaluepic_attributevalue_pic UNIQUE (attributevalue_id, pic_id),
|
||||||
|
CONSTRAINT UQ_attributevaluepic_attributevalue_sort UNIQUE (attributevalue_id, sort)
|
||||||
|
);
|
||||||
|
END
|
||||||
|
`;
|
||||||
|
|
||||||
export const INIT_ARTICLELISTARTICLE = `
|
export const INIT_ARTICLELISTARTICLE = `
|
||||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlelistarticle')
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlelistarticle')
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -1183,6 +1199,7 @@ export const SCHEMA_SCRIPTS = [
|
|||||||
INIT_ARTICLEEAN,
|
INIT_ARTICLEEAN,
|
||||||
INIT_ARTICLEPIC,
|
INIT_ARTICLEPIC,
|
||||||
INIT_BRANDPIC,
|
INIT_BRANDPIC,
|
||||||
|
INIT_ATTRIBUTEVALUEPIC,
|
||||||
INIT_ARTICLELISTARTICLE,
|
INIT_ARTICLELISTARTICLE,
|
||||||
INIT_OPTIONSLISTARTICLE,
|
INIT_OPTIONSLISTARTICLE,
|
||||||
INIT_CATEGORYARTICLE,
|
INIT_CATEGORYARTICLE,
|
||||||
|
|||||||
5
src/providers/sqlite3/queries/attributevaluepic.js
Normal file
5
src/providers/sqlite3/queries/attributevaluepic.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import * as source from '../../mssql/queries/attributevaluepic.js';
|
||||||
|
import { translateQueries } from './translate.js';
|
||||||
|
|
||||||
|
const queries = translateQueries(source, 'attributevaluepic');
|
||||||
|
export const { ADD, DELETE_BY_ATTRIBUTE_VALUE, GET_FIRST_PIC_BY_ATTRIBUTE_VALUE, GET_NEXT_SORT } = queries;
|
||||||
@@ -284,6 +284,19 @@ CREATE TABLE IF NOT EXISTS brandpic (
|
|||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const INIT_ATTRIBUTEVALUEPIC = `
|
||||||
|
CREATE TABLE IF NOT EXISTS attributevaluepic (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
attributevalue_id INTEGER NOT NULL,
|
||||||
|
pic_id INTEGER NOT NULL,
|
||||||
|
sort INTEGER NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE (attributevalue_id, pic_id),
|
||||||
|
UNIQUE (attributevalue_id, sort),
|
||||||
|
FOREIGN KEY (attributevalue_id) REFERENCES attributevalue(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
export const INIT_ARTICLELISTARTICLE = `
|
export const INIT_ARTICLELISTARTICLE = `
|
||||||
CREATE TABLE IF NOT EXISTS articlelistarticle (
|
CREATE TABLE IF NOT EXISTS articlelistarticle (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -853,6 +866,7 @@ export const SCHEMA_SCRIPTS = [
|
|||||||
INIT_ARTICLEEAN,
|
INIT_ARTICLEEAN,
|
||||||
INIT_ARTICLEPIC,
|
INIT_ARTICLEPIC,
|
||||||
INIT_BRANDPIC,
|
INIT_BRANDPIC,
|
||||||
|
INIT_ATTRIBUTEVALUEPIC,
|
||||||
INIT_ARTICLELISTARTICLE,
|
INIT_ARTICLELISTARTICLE,
|
||||||
INIT_OPTIONSLISTARTICLE,
|
INIT_OPTIONSLISTARTICLE,
|
||||||
INIT_CATEGORYARTICLE,
|
INIT_CATEGORYARTICLE,
|
||||||
|
|||||||
118
src/store/attributevalue/addImage.js
Normal file
118
src/store/attributevalue/addImage.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
ADD as MSSQL_ADD_AVPIC,
|
||||||
|
DELETE_BY_ATTRIBUTE_VALUE as MSSQL_DELETE_AVPIC,
|
||||||
|
} from '../../providers/mssql/queries/attributevaluepic.js';
|
||||||
|
import {
|
||||||
|
ADD as SQLITE_ADD_AVPIC,
|
||||||
|
DELETE_BY_ATTRIBUTE_VALUE as SQLITE_DELETE_AVPIC,
|
||||||
|
} from '../../providers/sqlite3/queries/attributevaluepic.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 resolveAttributeValueAccess from './resolveAttributeValueAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function addImage(provider) {
|
||||||
|
const access = resolveAttributeValueAccess(provider);
|
||||||
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
|
||||||
|
const deleteAvPicSql = isSqlite ? SQLITE_DELETE_AVPIC : MSSQL_DELETE_AVPIC;
|
||||||
|
const createPicSql = isSqlite ? SQLITE_CREATE_PIC : MSSQL_CREATE_PIC;
|
||||||
|
const addAvPicSql = isSqlite ? SQLITE_ADD_AVPIC : MSSQL_ADD_AVPIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an image to an attribute value, processing full, thumb, and 32px-edge small-thumb AVIF variants.
|
||||||
|
*
|
||||||
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
|
* @param {number} attributeValueId - The id of the attribute value.
|
||||||
|
* @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, pics: Array<object>}>}
|
||||||
|
*/
|
||||||
|
return async (googleAuthId, attributeValueId, image, tx) => {
|
||||||
|
const runAdd = async (runner) => {
|
||||||
|
await access(googleAuthId, attributeValueId, runner);
|
||||||
|
|
||||||
|
const processed = await processAvifImage(image);
|
||||||
|
|
||||||
|
// Clean existing attributevaluepic so the attribute value has 1 primary image
|
||||||
|
await runner.query(deleteAvPicSql, { attributeValueId });
|
||||||
|
|
||||||
|
const sort = 0;
|
||||||
|
const fullHash = newFullHash(processed.full.buffer);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
await runner.query(addAvPicSql, {
|
||||||
|
attributeValueId,
|
||||||
|
picId,
|
||||||
|
sort,
|
||||||
|
});
|
||||||
|
|
||||||
|
const picObj = {
|
||||||
|
fullHash,
|
||||||
|
thumbW: processed.thumb.w,
|
||||||
|
thumbH: processed.thumb.h,
|
||||||
|
thumbHash: processed.thumb.hash,
|
||||||
|
smallThumbW: processed.smallThumb.w,
|
||||||
|
smallThumbH: processed.smallThumb.h,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: attributeValueId,
|
||||||
|
fullHash,
|
||||||
|
thumbW: processed.thumb.w,
|
||||||
|
thumbH: processed.thumb.h,
|
||||||
|
thumbHash: processed.thumb.hash,
|
||||||
|
smallThumbW: processed.smallThumb.w,
|
||||||
|
smallThumbH: processed.smallThumb.h,
|
||||||
|
pics: [picObj],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
return runAdd(makeRunner(provider, tx));
|
||||||
|
}
|
||||||
|
if (typeof provider?.transaction === 'function') {
|
||||||
|
return provider.transaction(runAdd);
|
||||||
|
}
|
||||||
|
return runAdd(makeRunner(provider));
|
||||||
|
};
|
||||||
|
}
|
||||||
37
src/store/attributevalue/deleteImage.js
Normal file
37
src/store/attributevalue/deleteImage.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { DELETE_BY_ATTRIBUTE_VALUE as MSSQL_DELETE } from '../../providers/mssql/queries/attributevaluepic.js';
|
||||||
|
import { DELETE_BY_ATTRIBUTE_VALUE as SQLITE_DELETE } from '../../providers/sqlite3/queries/attributevaluepic.js';
|
||||||
|
import resolveAttributeValueAccess from './resolveAttributeValueAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function deleteImage(provider) {
|
||||||
|
const access = resolveAttributeValueAccess(provider);
|
||||||
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the image of an attribute value.
|
||||||
|
*
|
||||||
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
|
* @param {number} attributeValueId - The id of the attribute value.
|
||||||
|
* @param {object} [tx] - Optional transaction.
|
||||||
|
* @returns {Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null, pics: Array}>}
|
||||||
|
* @throws {Error} If attribute value not found or image not found.
|
||||||
|
*/
|
||||||
|
return async (googleAuthId, attributeValueId, tx) => {
|
||||||
|
const runner = makeRunner(provider, tx);
|
||||||
|
await access(googleAuthId, attributeValueId, runner);
|
||||||
|
|
||||||
|
const result = await runner.query(sql, { attributeValueId });
|
||||||
|
|
||||||
|
if (!result.rowsAffected) {
|
||||||
|
throw new Error('Image not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: attributeValueId,
|
||||||
|
thumbW: null,
|
||||||
|
thumbH: null,
|
||||||
|
thumbHash: null,
|
||||||
|
pics: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
22
src/store/attributevalue/resolveAttributeValueAccess.js
Normal file
22
src/store/attributevalue/resolveAttributeValueAccess.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { GET_BY_ID_AND_COMPANY as MSSQL_GET } from '../../providers/mssql/queries/attributevalue.js';
|
||||||
|
import { GET_BY_ID_AND_COMPANY as SQLITE_GET } from '../../providers/sqlite3/queries/attributevalue.js';
|
||||||
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function resolveAttributeValueAccess(provider) {
|
||||||
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||||
|
|
||||||
|
return async (googleAuthId, attributeValueId, tx) => {
|
||||||
|
if (!Number.isFinite(attributeValueId) || attributeValueId <= 0) {
|
||||||
|
throw new Error('Attribute value not found');
|
||||||
|
}
|
||||||
|
const { companyId } = await access(googleAuthId, tx);
|
||||||
|
const runner = makeRunner(provider, tx);
|
||||||
|
const result = await runner.query(sql, { id: attributeValueId, companyId });
|
||||||
|
if (!result.rows[0]) {
|
||||||
|
throw new Error('Attribute value not found');
|
||||||
|
}
|
||||||
|
return { companyId, attributeValue: result.rows[0] };
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user