From daf2d0525fe24b0eb2319d3e1f855dfd3fde7c50 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 9 Oct 2026 18:10:49 +0200 Subject: [PATCH] u --- docs/api/routes-all.md | 50 ++++++++++---------- docs/api/routes.md | 34 +++++++------- docs/api/routes/brand/_id/image.delete.md | 2 - docs/api/routes/brand/_id/image.post.md | 2 - docs/api/routes/brand/create.md | 2 - docs/api/routes/brand/list.md | 18 ++++++-- src/api/openapi.js | 6 +-- src/api/routes/brand.js | 56 +++++++++++++++++++++-- src/db/brand.js | 4 +- src/services/companyImport.js | 4 +- 10 files changed, 112 insertions(+), 66 deletions(-) diff --git a/docs/api/routes-all.md b/docs/api/routes-all.md index 1c9cb33..6504c9a 100644 --- a/docs/api/routes-all.md +++ b/docs/api/routes-all.md @@ -10,7 +10,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). ### Reusable types - `T1` = `{ article: T2 }` -- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T32, brand: T28|null, categoryIds: int[], photos: T26[], eans: str[], prices: T36[], suppliers: T12[] }` +- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T31, brand: T28|null, categoryIds: int[], photos: T26[], eans: str[], prices: T35[], suppliers: T12[] }` - `T3` = `{ delivery: T4 }` - `T4` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T20[], files: T9[], externalNumber?: str|null, createdAt?: str, ledger?: T40 }` - `T5` = `{ labeltemplate: T6 }` @@ -38,13 +38,13 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - `T27` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` - `T28` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` - `T29` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str }` -- `T30` = `{ id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` -- `T31` = `{ article: T34 }` -- `T32` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` -- `T33` = `{ id: int, iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- `T34` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` -- `T35` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` -- `T36` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T30` = `{ article: T33 }` +- `T31` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` +- `T32` = `{ id: int, iso639: str, name: str, taxrate: num, reducedtaxrate: num }` +- `T33` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` +- `T34` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` +- `T35` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T36` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` - `T37` = `{ id: int, entityId: int, contactType: str, contact: str }` - `T38` = `{ id: int, attributeId: int, value: str, sort: int }` - `T39` = `{ id: int, name: str, createdAt: str, updatedAt: str }` @@ -68,7 +68,7 @@ _none_ - Returns: `204 void` #### Create article in category — `POST /article` - Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }` -- Returns: `201 T31` +- Returns: `201 T30` #### Export articles as ZIP — `GET /article/export.zip` - Params: `query: categoryId?: int` - Returns: `200 zip` @@ -104,10 +104,10 @@ _none_ - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Create and attach a new child article to a parent article — `POST /article/{sku}/children` - Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }` -- Returns: `201 T31` +- Returns: `201 T30` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` -- Returns: `200 { article: T34, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` +- Returns: `200 { article: T33, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` - Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Returns: `200 { success: bool, deleted: bool }` @@ -134,7 +134,7 @@ _none_ - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` #### Toggle parent mode for an article — `POST /article/{sku}/parent` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` -- Returns: `200 T31` +- Returns: `200 T30` #### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }` - Returns: `200 T1` @@ -203,22 +203,22 @@ _none_ - Returns: `200 { attributevalue: T38 }` #### Login with Google ID token — `POST /auth/login` - Params: `body: { idToken: str }` -- Returns: `200 { message: str, user: T35 }` +- Returns: `200 { message: str, user: T34 }` #### Logout — `POST /auth/logout` _none_ - Returns: `200 { message: str }` #### Current user — `GET /auth/me` _none_ -- Returns: `200 { user: T35, googleClientId: str }` +- Returns: `200 { user: T34, googleClientId: str }` #### Set selected company — `PUT /auth/selected-company` - Params: `body: { companyId: int }` -- Returns: `200 { user: T35 }` +- Returns: `200 { user: T34 }` #### List brands — `GET /brand` -_none_ -- Returns: `200 { brands: { id: int, name: str, pics: T30[], gsprpinfo?: str|null }[] }` +- Params: `query: offset?: int, limit?: int` +- Returns: `200 { brands: { id: int, name: str, pics: T36[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` #### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `201 { brand: { id: int, name: str, pics: T30[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T36[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` @@ -227,10 +227,10 @@ _none_ - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` -- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T30[] } }` +- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T36[] } }` #### Add or replace brand image — `POST /brand/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T30[] } }` +- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T36[] } }` #### Create category — `POST /category` - Params: `body: { parentId: int, name: str }` - Returns: `201 { category: { id: int, parentId: int|null, sort: int, name: str } }` @@ -275,10 +275,10 @@ _none_ - Returns: `200 { ok: bool }` #### List companies — `GET /company` _none_ -- Returns: `200 { companies: T33[] }` +- Returns: `200 { companies: T32[] }` #### Create company — `POST /company` - Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- Returns: `201 { company: T33, user: T35 }` +- Returns: `201 { company: T32, user: T34 }` #### Get company history — `GET /company/history` - Params: `query: limit?: int` - Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }` @@ -302,7 +302,7 @@ _none_ - Returns: `204 void` #### Update company — `PUT /company/{id}` - Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- Returns: `200 { company: T33 }` +- Returns: `200 { company: T32 }` #### List customer groups — `GET /customergroup` _none_ - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` @@ -611,10 +611,10 @@ _none_ - Returns: `204 void` #### List units — `GET /unit` _none_ -- Returns: `200 { units: T32[] }` +- Returns: `200 { units: T31[] }` #### Upsert unit — `PUT /unit` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` -- Returns: `200 { unit: T32 }` +- Returns: `200 { unit: T31 }` #### JTL-POS category synchronization — `GET /v1/category` - Params: `query: lastChangedCategory?: str, limit?: int` - Returns: `200 {}[]` diff --git a/docs/api/routes.md b/docs/api/routes.md index 37886d6..44fa3dc 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -10,7 +10,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). ### Reusable types - `T1` = `{ article: T2 }` -- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T30, brand: T27|null, categoryIds: int[], photos: T25[], eans: str[], prices: T32[], suppliers: T11[] }` +- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T29, brand: T27|null, categoryIds: int[], photos: T25[], eans: str[], prices: T31[], suppliers: T11[] }` - `T3` = `{ delivery: T4 }` - `T4` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T19[], files: T9[], externalNumber?: str|null, createdAt?: str, ledger?: T36 }` - `T5` = `{ labeltemplate: T6 }` @@ -36,11 +36,11 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - `T25` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }` - `T26` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` - `T27` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` -- `T28` = `{ id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` -- `T29` = `{ article: T31 }` -- `T30` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` -- `T31` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` -- `T32` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T28` = `{ article: T30 }` +- `T29` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` +- `T30` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` +- `T31` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T32` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` - `T33` = `{ id: int, entityId: int, contactType: str, contact: str }` - `T34` = `{ id: int, attributeId: int, value: str, sort: int }` - `T35` = `{ id: int, name: str, createdAt: str, updatedAt: str }` @@ -63,7 +63,7 @@ _none_ - Returns: `204 void` #### Create article in category — `POST /article` - Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }` -- Returns: `201 T29` +- Returns: `201 T28` #### Delete article by SKU — `DELETE /article/{sku}` - Params: `sku: str, query: cascade?: bool` - Returns: `204 void` @@ -96,10 +96,10 @@ _none_ - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Create and attach a new child article to a parent article — `POST /article/{sku}/children` - Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }` -- Returns: `201 T29` +- Returns: `201 T28` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` -- Returns: `200 { article: T31, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` +- Returns: `200 { article: T30, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` - Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Returns: `200 { success: bool, deleted: bool }` @@ -123,7 +123,7 @@ _none_ - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` #### Toggle parent mode for an article — `POST /article/{sku}/parent` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` -- Returns: `200 T29` +- Returns: `200 T28` #### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }` - Returns: `200 T1` @@ -197,11 +197,11 @@ _none_ _none_ - Returns: `200 { message: str }` #### List brands — `GET /brand` -_none_ -- Returns: `200 { brands: { id: int, name: str, pics: T28[], gsprpinfo?: str|null }[] }` +- Params: `query: offset?: int, limit?: int` +- Returns: `200 { brands: { id: int, name: str, pics: T32[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` #### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `201 { brand: { id: int, name: str, pics: T28[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T32[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` @@ -210,10 +210,10 @@ _none_ - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` -- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T28[] } }` +- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T32[] } }` #### Add or replace brand image — `POST /brand/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T28[] } }` +- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T32[] } }` #### Create category — `POST /category` - Params: `body: { parentId: int, name: str }` - Returns: `201 { category: { id: int, parentId: int|null, sort: int, name: str } }` @@ -504,10 +504,10 @@ _none_ - Returns: `204 void` #### List units — `GET /unit` _none_ -- Returns: `200 { units: T30[] }` +- Returns: `200 { units: T29[] }` #### Upsert unit — `PUT /unit` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` -- Returns: `200 { unit: T30 }` +- Returns: `200 { unit: T29 }` #### List warehouses — `GET /warehouse` _none_ - Returns: `200 { warehouses: T40[] }` diff --git a/docs/api/routes/brand/_id/image.delete.md b/docs/api/routes/brand/_id/image.delete.md index 3716a8a..2d48b95 100644 --- a/docs/api/routes/brand/_id/image.delete.md +++ b/docs/api/routes/brand/_id/image.delete.md @@ -29,8 +29,6 @@ Image removed "thumbHash": "…", "pics": [ { - "id": 1, - "sort": 1, "fullHash": "…", "thumbW": 1, "thumbH": 1, diff --git a/docs/api/routes/brand/_id/image.post.md b/docs/api/routes/brand/_id/image.post.md index d6a61f3..80053cd 100644 --- a/docs/api/routes/brand/_id/image.post.md +++ b/docs/api/routes/brand/_id/image.post.md @@ -41,8 +41,6 @@ Image stored and linked "thumbHash": "…", "pics": [ { - "id": 1, - "sort": 1, "fullHash": "…", "thumbW": 1, "thumbH": 1, diff --git a/docs/api/routes/brand/create.md b/docs/api/routes/brand/create.md index aea6c9e..bc4ab14 100644 --- a/docs/api/routes/brand/create.md +++ b/docs/api/routes/brand/create.md @@ -39,8 +39,6 @@ Created brand "gsprpinfo": "…", "pics": [ { - "id": 1, - "sort": 1, "fullHash": "…", "thumbW": 1, "thumbH": 1, diff --git a/docs/api/routes/brand/list.md b/docs/api/routes/brand/list.md index 8dd1ed8..8718f77 100644 --- a/docs/api/routes/brand/list.md +++ b/docs/api/routes/brand/list.md @@ -4,7 +4,14 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). List brands -Lists brands for the authenticated user's selected company, sorted by name. +Lists brands for the authenticated user's selected company, sorted by name. Paged; `total` is the unpaged count. + +## Query parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `offset` | no | integer | Number of brands to skip | +| `limit` | no | integer | Number of brands to return | ## Response `200` @@ -19,8 +26,6 @@ Brand list "gsprpinfo": "…", "pics": [ { - "id": 1, - "sort": 1, "fullHash": "…", "thumbW": 1, "thumbH": 1, @@ -28,7 +33,10 @@ Brand list } ] } - ] + ], + "total": 1, + "offset": 1, + "limit": 1 } ``` @@ -36,7 +44,7 @@ Brand list | Status | Description | |--------|-------------| -| 400 | No company selected — `{"error":"…"}` | +| 400 | Invalid offset, invalid limit, or no company selected — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | | 403 | Not authorized — `{"error":"…"}` | | 404 | Company not found — `{"error":"…"}` | diff --git a/src/api/openapi.js b/src/api/openapi.js index 4fd949e..8f86488 100644 --- a/src/api/openapi.js +++ b/src/api/openapi.js @@ -343,12 +343,8 @@ * $ref: '#/components/schemas/SupplierArticleInfo' * BrandPic: * type: object - * required: [id, sort, fullHash, thumbW, thumbH, thumbHash] + * required: [fullHash, thumbW, thumbH, thumbHash] * properties: - * id: - * type: integer - * sort: - * type: integer * fullHash: * type: string * thumbW: diff --git a/src/api/routes/brand.js b/src/api/routes/brand.js index e333ee7..0b7f4a4 100644 --- a/src/api/routes/brand.js +++ b/src/api/routes/brand.js @@ -19,6 +19,8 @@ function brandErrorStatus(error) { if (error.message === 'Company not found') return 404; if (error.message === 'Brand not found') return 404; if (error.message === 'Invalid brand') return 400; + if (error.message === 'Invalid offset') return 400; + if (error.message === 'Invalid limit') return 400; if (error.message === 'Brand already exists') return 409; if (error.message === 'Brand in use') return 409; if (error.message === 'Image not found') return 404; @@ -45,9 +47,25 @@ export function register(app, basePath = '') { * get: * tags: [Brand] * summary: List brands - * description: Lists brands for the authenticated user's selected company, sorted by name. + * description: Lists brands for the authenticated user's selected company, sorted by name. Paged; `total` is the unpaged count. * security: * - cookieAuth: [] + * parameters: + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * minimum: 0 + * description: Number of brands to skip + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * minimum: 1 + * maximum: 100 + * description: Number of brands to return * x-markdown: brand/list.md * responses: * 200: @@ -56,13 +74,23 @@ export function register(app, basePath = '') { * application/json: * schema: * type: object + * required: [brands, total, offset, limit] * properties: * brands: * type: array * items: * $ref: '#/components/schemas/Brand' + * total: + * type: integer + * description: Total number of unpaged brands + * offset: + * type: integer + * description: Number of brands skipped + * limit: + * type: integer + * description: Number of brands returned * 400: - * description: No company selected + * description: Invalid offset, invalid limit, or no company selected * content: * application/json: * schema: @@ -92,9 +120,29 @@ export function register(app, basePath = '') { return res.status(400).json({ error: selected.error }); } + const rawOffset = req.query.offset; + const rawLimit = req.query.limit; + + let offset = 0; + let limit = 10; + + if (rawOffset !== undefined) { + offset = parseInt(rawOffset, 10); + if (!Number.isInteger(offset) || offset < 0) { + return res.status(400).json({ error: 'offset must be an integer >= 0' }); + } + } + + if (rawLimit !== undefined) { + limit = parseInt(rawLimit, 10); + if (!Number.isInteger(limit) || limit < 1 || limit > 100) { + return res.status(400).json({ error: 'limit must be an integer between 1 and 100' }); + } + } + try { - const brands = await db.list(req.user.id); - res.status(200).json({ brands }); + const result = await db.list(req.user.id, { offset, limit }); + res.status(200).json(result); } catch (error) { const status = brandErrorStatus(error); if (status === 500) console.error('List brands failed:', error); diff --git a/src/db/brand.js b/src/db/brand.js index 47eb24d..ada4fcd 100644 --- a/src/db/brand.js +++ b/src/db/brand.js @@ -1,8 +1,8 @@ import { getStore } from './store.js'; -export async function list(googleAuthId) { +export async function list(googleAuthId, opts) { const store = await getStore(); - return store.brand.list(googleAuthId); + return store.brand.list(googleAuthId, opts); } export async function create(googleAuthId, data) { diff --git a/src/services/companyImport.js b/src/services/companyImport.js index 3c6dd85..99fe268 100644 --- a/src/services/companyImport.js +++ b/src/services/companyImport.js @@ -360,9 +360,9 @@ export async function executeCompanyImport({ log.info(`Processing ${brandEntries.length} brands (Hersteller)...`); // Fetch existing brands to accurately track new vs existing - const existingBrands = await store.brand.list(googleAuthId); + const { brands: existingBrandRows } = await store.brand.list(googleAuthId, { offset: 0, limit: 100 }); const existingBrandNames = new Set( - existingBrands.map((b) => (b.name || '').trim().toLowerCase()).filter(Boolean) + existingBrandRows.map((b) => (b.name || '').trim().toLowerCase()).filter(Boolean) ); for (let i = 0; i < brandEntries.length; i++) {