This commit is contained in:
seb
2026-10-09 18:10:51 +02:00
parent 4ae3439289
commit 07c4f0bff9
8 changed files with 130 additions and 43 deletions

View File

@@ -39,7 +39,7 @@ Creates a brand for the user's selected company.
**Returns** **Returns**
`Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` — The created brand. `Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` — The created brand.
**Throws** **Throws**
@@ -84,24 +84,27 @@ await store.brand.delete(42, 3);
*Source: `src/store/brand/list.js`* *Source: `src/store/brand/list.js`*
Lists the brands of the user's selected company. Lists the brands of the user's selected company, sorted by name.
**Parameters** **Parameters**
| Name | Type | Description | | Name | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. | | `googleAuthId` | `number` | The id of the Google auth user. |
| `[opts]` | `object` | Optional pagination options. |
| `[opts.offset=0]` | `number` | Number of brands to skip. |
| `[opts.limit=10]` | `number` | Number of brands to return (max 100). |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
`Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>` — The brands. `Promise<{brands: Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>, total: number, offset: number, limit: number}>` — The brands plus the total unpaged count.
**Example** **Example**
```js ```js
const brands = await store.brand.list(42); const result = await store.brand.list(42, { offset: 0, limit: 10 });
// => [{ id, name, gsprpinfo, pics: [{ sort, fullHash, thumbW, thumbH, thumbHash }] }] // => { brands: [{ id, name, gsprpinfo, pics: [...] }], total: 25, offset: 0, limit: 10 }
``` ```
## `brand.update` ## `brand.update`
@@ -154,7 +157,7 @@ Creates or updates a brand for the user's selected company, keyed by name.
**Returns** **Returns**
`Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` — The upserted brand. `Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` — The upserted brand.
**Throws** **Throws**

6
API.md
View File

@@ -130,11 +130,11 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `set(googleAuthId: number, input: {key: string, value: string, frontend?: boolean, writeonly?: boolean}, tx?: object): Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>` - `set(googleAuthId: number, input: {key: string, value: string, frontend?: boolean, writeonly?: boolean}, tx?: object): Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>`
- [brand](API.brand.md) - [brand](API.brand.md)
- `addImage(googleAuthId: number, brandId: number, image: Buffer, tx?: object): Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, pics: Array<object>}>` - `addImage(googleAuthId: number, brandId: number, image: Buffer, tx?: object): Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, pics: Array<object>}>`
- `create(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` - `create(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>`
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>` - `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>` - `list(googleAuthId: number, opts?: {offset?: number, limit?: number}, tx?: object): Promise<{brands: Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>, total: number, offset: number, limit: number}>`
- `update(googleAuthId: number, id: number, input: {name?: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, touchedArticleCount: number}>` - `update(googleAuthId: number, id: number, input: {name?: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, touchedArticleCount: number}>`
- `upsert(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` - `upsert(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>`
- [customergroup](API.customergroup.md) - [customergroup](API.customergroup.md)
- `create(googleAuthId: number, input: {name: string}, tx?: object): Promise<{id: number, name: string, sort: number}>` - `create(googleAuthId: number, input: {name: string}, tx?: object): Promise<{id: number, name: string, sort: number}>`
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>` - `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`

View File

@@ -5,8 +5,6 @@ SELECT
b.gsprpinfo, b.gsprpinfo,
( (
SELECT SELECT
bp.id,
bp.sort,
p.hash AS fullHash, p.hash AS fullHash,
p.thumbw AS thumbW, p.thumbw AS thumbW,
p.thumbh AS thumbH, p.thumbh AS thumbH,
@@ -22,6 +20,35 @@ WHERE b.company_id = @companyId
ORDER BY b.name ORDER BY b.name
`; `;
export const COUNT_BY_COMPANY = `
SELECT COUNT(*) AS total
FROM brand b
WHERE b.company_id = @companyId
`;
export const LIST_BY_COMPANY_PAGED = `
SELECT
b.id,
b.name,
b.gsprpinfo,
(
SELECT
p.hash AS fullHash,
p.thumbw AS thumbW,
p.thumbh AS thumbH,
p.thumbhash AS thumbHash
FROM brandpic bp
INNER JOIN pic p ON p.id = bp.pic_id
WHERE bp.brand_id = b.id
ORDER BY bp.sort ASC, bp.id ASC
FOR JSON PATH
) AS picsJson
FROM brand b
WHERE b.company_id = @companyId
ORDER BY b.name
OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY
`;
export const GET_BY_COMPANY_AND_NAME = ` export const GET_BY_COMPANY_AND_NAME = `
SELECT SELECT
b.id, b.id,
@@ -29,8 +56,6 @@ SELECT
b.gsprpinfo, b.gsprpinfo,
( (
SELECT SELECT
bp.id,
bp.sort,
p.hash AS fullHash, p.hash AS fullHash,
p.thumbw AS thumbW, p.thumbw AS thumbW,
p.thumbh AS thumbH, p.thumbh AS thumbH,

View File

@@ -4,6 +4,7 @@ import { translateQueries } from './translate.js';
const queries = translateQueries(source, 'brand'); const queries = translateQueries(source, 'brand');
export const { export const {
CREATE, DELETE_BY_COMPANY_AND_ID, UPDATE_BY_ID, GET_BY_ID_AND_COMPANY, CREATE, DELETE_BY_COMPANY_AND_ID, UPDATE_BY_ID, GET_BY_ID_AND_COMPANY,
COUNT_BY_COMPANY,
} = queries; } = queries;
export const LIST_BY_COMPANY = ` export const LIST_BY_COMPANY = `
@@ -13,15 +14,13 @@ SELECT
b.gsprpinfo, b.gsprpinfo,
( (
SELECT COALESCE(json_group_array(json_object( SELECT COALESCE(json_group_array(json_object(
'id', id,
'sort', sort,
'fullHash', hash, 'fullHash', hash,
'thumbW', thumbw, 'thumbW', thumbw,
'thumbH', thumbh, 'thumbH', thumbh,
'thumbHash', thumbhash 'thumbHash', thumbhash
)), '[]') )), '[]')
FROM ( FROM (
SELECT bp.id, bp.sort, p.hash, p.thumbw, p.thumbh, p.thumbhash SELECT p.hash, p.thumbw, p.thumbh, p.thumbhash
FROM brandpic bp FROM brandpic bp
INNER JOIN pic p ON p.id = bp.pic_id INNER JOIN pic p ON p.id = bp.pic_id
WHERE bp.brand_id = b.id WHERE bp.brand_id = b.id
@@ -33,6 +32,32 @@ WHERE b.company_id = @companyId
ORDER BY b.name ORDER BY b.name
`; `;
export const LIST_BY_COMPANY_PAGED = `
SELECT
b.id,
b.name,
b.gsprpinfo,
(
SELECT COALESCE(json_group_array(json_object(
'fullHash', hash,
'thumbW', thumbw,
'thumbH', thumbh,
'thumbHash', thumbhash
)), '[]')
FROM (
SELECT p.hash, p.thumbw, p.thumbh, p.thumbhash
FROM brandpic bp
INNER JOIN pic p ON p.id = bp.pic_id
WHERE bp.brand_id = b.id
ORDER BY bp.sort, bp.id
)
) AS picsJson
FROM brand b
WHERE b.company_id = @companyId
ORDER BY b.name
LIMIT @limit OFFSET @offset
`;
export const GET_BY_COMPANY_AND_NAME = ` export const GET_BY_COMPANY_AND_NAME = `
SELECT SELECT
b.id, b.id,
@@ -40,15 +65,13 @@ SELECT
b.gsprpinfo, b.gsprpinfo,
( (
SELECT COALESCE(json_group_array(json_object( SELECT COALESCE(json_group_array(json_object(
'id', id,
'sort', sort,
'fullHash', hash, 'fullHash', hash,
'thumbW', thumbw, 'thumbW', thumbw,
'thumbH', thumbh, 'thumbH', thumbh,
'thumbHash', thumbhash 'thumbHash', thumbhash
)), '[]') )), '[]')
FROM ( FROM (
SELECT bp.id, bp.sort, p.hash, p.thumbw, p.thumbh, p.thumbhash SELECT p.hash, p.thumbw, p.thumbh, p.thumbhash
FROM brandpic bp FROM brandpic bp
INNER JOIN pic p ON p.id = bp.pic_id INNER JOIN pic p ON p.id = bp.pic_id
WHERE bp.brand_id = b.id WHERE bp.brand_id = b.id

View File

@@ -82,11 +82,8 @@ export default function addImage(provider) {
picId, picId,
sort, sort,
}); });
const brandPicId = brandPicResult.rows[0]?.id ?? brandPicResult.insertId;
const picObj = { const picObj = {
id: brandPicId,
sort,
fullHash, fullHash,
thumbW: processed.thumb.w, thumbW: processed.thumb.w,
thumbH: processed.thumb.h, thumbH: processed.thumb.h,

View File

@@ -26,7 +26,7 @@ export default function create(provider) {
* @param {string} input.name - The brand name. * @param {string} input.name - The brand name.
* @param {string} [input.gsprpinfo] - Optional GSPRP info. * @param {string} [input.gsprpinfo] - Optional GSPRP info.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The created brand. * @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The created brand.
* @throws {Error} If the brand is invalid or already exists. * @throws {Error} If the brand is invalid or already exists.
* *
* @example * @example

View File

@@ -1,5 +1,13 @@
import { LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY } from '../../providers/mssql/queries/brand.js'; import {
import { LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY } from '../../providers/sqlite3/queries/brand.js'; COUNT_BY_COMPANY as MSSQL_COUNT,
LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY,
LIST_BY_COMPANY_PAGED as MSSQL_LIST_PAGED,
} from '../../providers/mssql/queries/brand.js';
import {
COUNT_BY_COMPANY as SQLITE_COUNT,
LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY,
LIST_BY_COMPANY_PAGED as SQLITE_LIST_PAGED,
} from '../../providers/sqlite3/queries/brand.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -11,32 +19,63 @@ function parsePics(raw) {
export default function list(provider) { export default function list(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const sql = const isSqlite = provider?.dialect === 'sqlite3';
provider?.dialect === 'sqlite3' const sql = isSqlite ? SQLITE_LIST_BY_COMPANY : MSSQL_LIST_BY_COMPANY;
? SQLITE_LIST_BY_COMPANY const sqlCount = isSqlite ? SQLITE_COUNT : MSSQL_COUNT;
: MSSQL_LIST_BY_COMPANY; const sqlPaged = isSqlite ? SQLITE_LIST_PAGED : MSSQL_LIST_PAGED;
/** /**
* Lists the brands of the user's selected company. * Lists the brands of the user's selected company, sorted by name.
* *
* @param {number} googleAuthId - The id of the Google auth user. * @param {number} googleAuthId - The id of the Google auth user.
* @param {object} [opts] - Optional pagination options.
* @param {number} [opts.offset=0] - Number of brands to skip.
* @param {number} [opts.limit=10] - Number of brands to return (max 100).
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>} The brands. * @returns {Promise<{brands: Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>, total: number, offset: number, limit: number}>} The brands plus the total unpaged count.
* *
* @example * @example
* const brands = await store.brand.list(42); * const result = await store.brand.list(42, { offset: 0, limit: 10 });
* // => [{ id, name, gsprpinfo, pics: [{ sort, fullHash, thumbW, thumbH, thumbHash }] }] * // => { brands: [{ id, name, gsprpinfo, pics: [...] }], total: 25, offset: 0, limit: 10 }
*/ */
return async (googleAuthId, tx) => { return async (googleAuthId, opts, tx) => {
const { companyId } = await access(googleAuthId, tx); let offset = 0;
let limit = 10;
let resolvedTx = tx;
const result = await makeRunner(provider, tx).query(sql, { companyId }); if (opts != null && typeof opts === 'object' && !opts._isTx) {
const parsedOffset = Number(opts.offset ?? 0);
const parsedLimit = Number(opts.limit ?? 10);
if (!Number.isInteger(parsedOffset) || parsedOffset < 0) {
throw new Error('Invalid offset');
}
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 100) {
throw new Error('Invalid limit');
}
offset = parsedOffset;
limit = parsedLimit;
} else if (opts != null) {
resolvedTx = opts;
}
return result.rows.map((row) => ({ const runner = makeRunner(provider, resolvedTx);
id: row.id, const { companyId } = await access(googleAuthId, runner);
name: row.name,
gsprpinfo: row.gsprpinfo, const [countResult, listResult] = await Promise.all([
pics: parsePics(row.picsJson), runner.query(sqlCount, { companyId }),
})); runner.query(sqlPaged, { companyId, offset, limit }),
]);
return {
brands: listResult.rows.map((row) => ({
id: row.id,
name: row.name,
gsprpinfo: row.gsprpinfo,
pics: parsePics(row.picsJson),
})),
total: Number(countResult.rows[0]?.total ?? 0),
offset,
limit,
};
}; };
} }

View File

@@ -34,7 +34,7 @@ export default function upsert(provider) {
* @param {string} input.name - The brand name. * @param {string} input.name - The brand name.
* @param {string} [input.gsprpinfo] - Optional GSPRP info. * @param {string} [input.gsprpinfo] - Optional GSPRP info.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The upserted brand. * @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The upserted brand.
* @throws {Error} If the brand is invalid. * @throws {Error} If the brand is invalid.
* *
* @example * @example