u
This commit is contained in:
@@ -12,6 +12,7 @@ import { authMiddleware } from './middleware/auth.js';
|
||||
import { ensureConnection } from '../db/store.js';
|
||||
import { closeAll as closeAllSse } from './sse.js';
|
||||
import { closeAllManagers } from '../services/conversationManager.js';
|
||||
import { shutdownImageCropWorkers } from '../services/imageCrop.js';
|
||||
import { getOrCreateCert } from '../util/cert.js';
|
||||
import v1Router from '../v1/index.js';
|
||||
|
||||
@@ -192,6 +193,9 @@ export async function stopApi() {
|
||||
// forever — node --watch hangs on "Waiting for graceful termination...".
|
||||
closeAllSse();
|
||||
closeAllManagers();
|
||||
// Pooled image-crop workers are live threads and must be terminated too, or the
|
||||
// process stays alive after the last request has been served.
|
||||
await shutdownImageCropWorkers();
|
||||
const closers = [];
|
||||
if (server) {
|
||||
const s = server;
|
||||
|
||||
@@ -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 = '') {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user