This commit is contained in:
seb
2026-10-04 23:39:49 +02:00
parent 9feca54a0b
commit ab40a38a75
10 changed files with 1217 additions and 865 deletions

View File

@@ -1544,124 +1544,6 @@ export function register(app, basePath = '') {
}
});
/**
* @openapi
* /articleIndex:
* get:
* tags: [Article]
* summary: List article search index
* description: |
* Returns every article reachable via the company's category tree.
* Designed for client-side search / autocomplete preload.
* Auth via company access.
*
* REQUIRES `windowSize`. Without it the response is always an empty list — the full
* index can be very large and is not useful to a caller that did not ask for a
* specific window. Pass the number of entries the client actually intends to preload.
* security:
* - cookieAuth: []
* parameters:
* - in: query
* name: windowSize
* required: true
* schema:
* type: integer
* minimum: 1
* maximum: 1000
* description: |
* Number of articles to return (window size). Required; omitting it returns an
* empty `articles` array. `truncated` is true when more entries exist than were
* returned, signalling the client should request a larger window or narrow its
* query.
* responses:
* 200:
* description: Search index
* content:
* application/json:
* schema:
* type: object
* properties:
* articles:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* nullable: true
* eans:
* type: array
* items:
* type: string
* categoryIds:
* type: array
* items:
* type: integer
* truncated:
* type: boolean
* description: True when the window was smaller than the total number of matching articles.
* 400:
* description: No company selected
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 401:
* description: Unauthorized
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 403:
* description: Not authorized
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 404:
* description: Company not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
app.get(`${basePath}/articleIndex`, async (req, res) => {
const companyId = req.user.selectedCompanyId;
if (companyId == null) {
return res.status(400).json({ error: 'No company selected' });
}
// A window size must be given explicitly. Without one the index can be arbitrarily large,
// and returning the entire result set to a caller that never asked for a window is
// exactly the "fetch everything" behaviour this guard exists to prevent.
const rawWindowSize = req.query.windowSize;
if (rawWindowSize === undefined || rawWindowSize === '') {
return res.status(200).json({ articles: [], truncated: false });
}
const windowSize = parseInt(rawWindowSize, 10);
if (!Number.isInteger(windowSize) || windowSize < 1 || windowSize > 1000) {
return res.status(400).json({ error: 'windowSize must be an integer between 1 and 1000' });
}
try {
const all = await db.listSearchIndex(req.user.id);
// `length === windowSize` is the same truncation signal used by
// GET /article/{sku}/history: the window is exactly full, so more may exist.
const truncated = all.length > windowSize;
const articles = truncated ? all.slice(0, windowSize) : all;
res.status(200).json({ articles, truncated });
} catch (error) {
const status = articleErrorStatus(error);
if (status === 500) console.error('List article search index failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/print:
@@ -2448,4 +2330,3 @@ export function register(app, basePath = '') {
});
}