u
This commit is contained in:
@@ -182,11 +182,11 @@ _none_
|
|||||||
#### Toggle parent mode for an article — `POST /article/{sku}/parent`
|
#### Toggle parent mode for an article — `POST /article/{sku}/parent`
|
||||||
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
|
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices`
|
#### Replace POS prices by SKU (net > gross) — `PUT /article/{sku}/prices`
|
||||||
- Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }`
|
- Params: `sku: str, body: { prices: { net?: num, gross?: num, taxRate?: num, minmultiplier?: num }[] }`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Print article label — `POST /article/{sku}/print`
|
#### Print article label — `POST /article/{sku}/print`
|
||||||
- Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }`
|
- Params: `sku: str, body: { templateId: int, agentprinterId: int, quantity: int }`
|
||||||
- Returns: `200 { success: bool }`
|
- Returns: `200 { success: bool }`
|
||||||
#### List article properties — `GET /article/{sku}/property`
|
#### List article properties — `GET /article/{sku}/property`
|
||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
@@ -559,7 +559,7 @@ _none_
|
|||||||
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] } }`
|
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] } }`
|
||||||
- Returns: `201 { labeltemplate: T9 }`
|
- Returns: `201 { labeltemplate: T9 }`
|
||||||
#### Test print label template — `POST /labeltemplate/testprint`
|
#### Test print label template — `POST /labeltemplate/testprint`
|
||||||
- Params: `body: { printerId?: int, agentprinterId?: int, templateId?: int, objects?: any[], width?: int, height?: int, dpi?: int, sku?: str, quantity?: int }`
|
- Params: `body: { agentprinterId: int, templateId?: int, objects?: any[], width?: int, height?: int, dpi?: int, sku?: str, quantity?: int }`
|
||||||
- Returns: `200 { success: bool }`
|
- Returns: `200 { success: bool }`
|
||||||
#### Delete label template — `DELETE /labeltemplate/{id}`
|
#### Delete label template — `DELETE /labeltemplate/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
|
|||||||
@@ -145,11 +145,11 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
#### Toggle parent mode for an article — `POST /article/{sku}/parent`
|
#### Toggle parent mode for an article — `POST /article/{sku}/parent`
|
||||||
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
|
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices`
|
#### Replace POS prices by SKU (net > gross) — `PUT /article/{sku}/prices`
|
||||||
- Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }`
|
- Params: `sku: str, body: { prices: { net?: num, gross?: num, taxRate?: num, minmultiplier?: num }[] }`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Print article label — `POST /article/{sku}/print`
|
#### Print article label — `POST /article/{sku}/print`
|
||||||
- Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }`
|
- Params: `sku: str, body: { templateId: int, agentprinterId: int, quantity: int }`
|
||||||
- Returns: `200 { success: bool }`
|
- Returns: `200 { success: bool }`
|
||||||
#### List article properties — `GET /article/{sku}/property`
|
#### List article properties — `GET /article/{sku}/property`
|
||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
@@ -431,7 +431,7 @@ _none_
|
|||||||
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T19[] } }`
|
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T19[] } }`
|
||||||
- Returns: `201 { labeltemplate: T6 }`
|
- Returns: `201 { labeltemplate: T6 }`
|
||||||
#### Test print label template — `POST /labeltemplate/testprint`
|
#### Test print label template — `POST /labeltemplate/testprint`
|
||||||
- Params: `body: { printerId?: int, agentprinterId?: int, templateId?: int, objects?: any[], width?: int, height?: int, dpi?: int, sku?: str, quantity?: int }`
|
- Params: `body: { agentprinterId: int, templateId?: int, objects?: any[], width?: int, height?: int, dpi?: int, sku?: str, quantity?: int }`
|
||||||
- Returns: `200 { success: bool }`
|
- Returns: `200 { success: bool }`
|
||||||
#### Delete label template — `DELETE /labeltemplate/{id}`
|
#### Delete label template — `DELETE /labeltemplate/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ Prints a label for the article using a label template.
|
|||||||
| Field | Required | Type | Description |
|
| Field | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `templateId` | yes | integer | |
|
| `templateId` | yes | integer | |
|
||||||
| `printerId` | yes | integer | |
|
| `agentprinterId` | yes | integer | |
|
||||||
| `quantity` | yes | integer | |
|
| `quantity` | yes | integer | |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"templateId": 1,
|
"templateId": 1,
|
||||||
"printerId": 1,
|
"agentprinterId": 1,
|
||||||
"quantity": 1
|
"quantity": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
Replace POS prices by SKU (net > gross; price=net, tax=taxRate %)
|
Replace POS prices by SKU (net > gross)
|
||||||
|
|
||||||
Replace-all prices for the company POS customer group.
|
Replace-all prices for the company POS customer group.
|
||||||
`net` takes precedence over `price` (legacy alias for net) and `gross`.
|
`net` takes precedence over `gross`.
|
||||||
`tax` and `taxRate` both specify the tax rate percentage (e.g. 19 for 19%), not monetary tax.
|
`taxRate` specifies the tax rate percentage (e.g. 19 for 19%), not monetary tax.
|
||||||
When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
|
When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
|
||||||
Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET).
|
Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET).
|
||||||
Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
||||||
@@ -23,11 +23,9 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
|||||||
| Field | Required | Type | Description |
|
| Field | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `prices` | yes | object[] | |
|
| `prices` | yes | object[] | |
|
||||||
| `prices[].net` | no | number | Net price (excl. VAT). Either `net`, `gross`, or legacy `price` is required. |
|
| `prices[].net` | no | number | Net price (excl. VAT). Either `net` or `gross` is required. |
|
||||||
| `prices[].gross` | no | number | Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`. |
|
| `prices[].gross` | no | number | Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`. |
|
||||||
| `prices[].price` | no | number | Legacy alias for `net`. |
|
|
||||||
| `prices[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). |
|
| `prices[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). |
|
||||||
| `prices[].tax` | no | number | Alias for `taxRate` percentage. |
|
|
||||||
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. |
|
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ Placeholders in text and barcodes are interpolated using a sample article or the
|
|||||||
|
|
||||||
| Field | Required | Type | Description |
|
| Field | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `printerId` | no | integer | |
|
| `agentprinterId` | yes | integer | Agent printer ID |
|
||||||
| `agentprinterId` | no | integer | |
|
|
||||||
| `templateId` | no | integer | |
|
| `templateId` | no | integer | |
|
||||||
| `objects` | no | any[] | |
|
| `objects` | no | any[] | |
|
||||||
| `width` | no | integer | |
|
| `width` | no | integer | |
|
||||||
@@ -24,7 +23,6 @@ Placeholders in text and barcodes are interpolated using a sample article or the
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"printerId": 1,
|
|
||||||
"agentprinterId": 1,
|
"agentprinterId": 1,
|
||||||
"templateId": 1,
|
"templateId": 1,
|
||||||
"objects": [],
|
"objects": [],
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ agentRouter.get('/events', agentAuthMiddleware, async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
async function handleState(req, res) {
|
async function handleState(req, res) {
|
||||||
try {
|
try {
|
||||||
const rawState = req.body?.state !== undefined ? req.body.state : (req.body?.clientstate !== undefined ? req.body.clientstate : req.body);
|
const rawState = req.body?.state;
|
||||||
setAgentState(req.agent.id, rawState);
|
setAgentState(req.agent.id, rawState);
|
||||||
|
|
||||||
const version = extractAgentVersion(req);
|
const version = extractAgentVersion(req);
|
||||||
@@ -377,9 +377,7 @@ async function handleState(req, res) {
|
|||||||
await db.touchLastseen(req.agent.id);
|
await db.touchLastseen(req.agent.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawPrinters = Array.isArray(rawState?.printers)
|
const rawPrinters = Array.isArray(rawState?.printers) ? rawState.printers : null;
|
||||||
? rawState.printers
|
|
||||||
: (Array.isArray(req.body?.printers) ? req.body.printers : null);
|
|
||||||
|
|
||||||
let anyAgentPrinterCreated = false;
|
let anyAgentPrinterCreated = false;
|
||||||
if (rawPrinters && rawPrinters.length > 0) {
|
if (rawPrinters && rawPrinters.length > 0) {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ function corsMiddleware(req, res, next) {
|
|||||||
const reqHeaders = req.headers['access-control-request-headers'];
|
const reqHeaders = req.headers['access-control-request-headers'];
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Access-Control-Allow-Headers',
|
'Access-Control-Allow-Headers',
|
||||||
reqHeaders || 'Content-Type, Authorization, x-agent-token, x-auth-token, authtoken, auth-token, token, x-token, agentversion, clientversion, version'
|
reqHeaders || 'Content-Type, Authorization, x-agent-token, agentversion'
|
||||||
);
|
);
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
||||||
if (req.method === 'OPTIONS') {
|
if (req.method === 'OPTIONS') {
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ export function extractAgentToken(req) {
|
|||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of ['x-agent-token', 'x-auth-token', 'authtoken', 'auth-token', 'token', 'x-token']) {
|
if (typeof req.headers['x-agent-token'] === 'string' && req.headers['x-agent-token'].trim()) {
|
||||||
const val = req.headers[key];
|
return req.headers['x-agent-token'].trim();
|
||||||
if (typeof val === 'string' && val.trim()) {
|
|
||||||
return val.trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryToken = req.query?.authToken || req.query?.token;
|
if (typeof req.query?.token === 'string' && req.query.token.trim()) {
|
||||||
if (typeof queryToken === 'string' && queryToken.trim()) {
|
return req.query.token.trim();
|
||||||
return queryToken.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@@ -27,13 +23,8 @@ export function extractAgentToken(req) {
|
|||||||
|
|
||||||
export function extractAgentVersion(req) {
|
export function extractAgentVersion(req) {
|
||||||
const raw = req.headers['agentversion'] ||
|
const raw = req.headers['agentversion'] ||
|
||||||
req.headers['agent-version'] ||
|
|
||||||
req.headers['clientversion'] ||
|
|
||||||
req.headers['version'] ||
|
|
||||||
req.query?.agentversion ||
|
req.query?.agentversion ||
|
||||||
req.query?.version ||
|
|
||||||
req.body?.agentversion ||
|
req.body?.agentversion ||
|
||||||
req.body?.version ||
|
|
||||||
null;
|
null;
|
||||||
return raw ? String(raw).trim() : null;
|
return raw ? String(raw).trim() : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ export function register(app, basePath = '') {
|
|||||||
* /article/{sku}/prices:
|
* /article/{sku}/prices:
|
||||||
* put:
|
* put:
|
||||||
* tags: [Article]
|
* tags: [Article]
|
||||||
* summary: Replace POS prices by SKU (net > gross; price=net, tax=taxRate %)
|
* summary: Replace POS prices by SKU (net > gross)
|
||||||
* description: |
|
* description: |
|
||||||
* Replace-all prices for the company POS customer group.
|
* Replace-all prices for the company POS customer group.
|
||||||
* `net` takes precedence over `price` (legacy alias for net) and `gross`.
|
* `net` takes precedence over `gross`.
|
||||||
* `tax` and `taxRate` both specify the tax rate percentage (e.g. 19 for 19%), not monetary tax.
|
* `taxRate` specifies the tax rate percentage (e.g. 19 for 19%), not monetary tax.
|
||||||
* When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
|
* When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
|
||||||
* Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET).
|
* Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET).
|
||||||
* Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
* Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
||||||
@@ -50,19 +50,13 @@ export function register(app, basePath = '') {
|
|||||||
* properties:
|
* properties:
|
||||||
* net:
|
* net:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Net price (excl. VAT). Either `net`, `gross`, or legacy `price` is required.
|
* description: Net price (excl. VAT). Either `net` or `gross` is required.
|
||||||
* gross:
|
* gross:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`.
|
* description: Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`.
|
||||||
* price:
|
|
||||||
* type: number
|
|
||||||
* description: Legacy alias for `net`.
|
|
||||||
* taxRate:
|
* taxRate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%).
|
* description: Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%).
|
||||||
* tax:
|
|
||||||
* type: number
|
|
||||||
* description: Alias for `taxRate` percentage.
|
|
||||||
* minmultiplier:
|
* minmultiplier:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Defaults to 1; must be unique per request.
|
* description: Defaults to 1; must be unique per request.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
parseFullHash,
|
parseFullHash,
|
||||||
articleErrorStatus,
|
articleErrorStatus,
|
||||||
interpolateString,
|
interpolateString,
|
||||||
|
interpolateObjects,
|
||||||
} from './shared.js';
|
} from './shared.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -433,16 +434,7 @@ export function register(app, basePath = '') {
|
|||||||
? content
|
? content
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const interpolatedObjects = objects.map(obj => {
|
const interpolatedObjects = interpolateObjects(objects, article);
|
||||||
const cloned = { ...obj };
|
|
||||||
if (typeof cloned.text === 'string') {
|
|
||||||
cloned.text = interpolateString(cloned.text, article);
|
|
||||||
}
|
|
||||||
if (typeof cloned.code === 'string') {
|
|
||||||
cloned.code = interpolateString(cloned.code, article);
|
|
||||||
}
|
|
||||||
return cloned;
|
|
||||||
});
|
|
||||||
|
|
||||||
const printPayload = await renderLabel({
|
const printPayload = await renderLabel({
|
||||||
width: template.width,
|
width: template.width,
|
||||||
|
|||||||
@@ -117,20 +117,57 @@ export function articleErrorStatus(error) {
|
|||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CURRENCY_FORMATTER = new Intl.NumberFormat('de-DE', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'EUR',
|
||||||
|
});
|
||||||
|
|
||||||
|
export function formatPrice(amount) {
|
||||||
|
if (amount == null || !Number.isFinite(Number(amount))) return '';
|
||||||
|
return CURRENCY_FORMATTER.format(Number(amount));
|
||||||
|
}
|
||||||
|
|
||||||
export function interpolateString(str, article) {
|
export function interpolateString(str, article) {
|
||||||
if (typeof str !== 'string') return str;
|
if (typeof str !== 'string') return str;
|
||||||
const net = article.net_price ?? article.prices?.[0]?.net;
|
const basePrice = Array.isArray(article?.prices) && article.prices.length > 0
|
||||||
const taxRate = article.prices?.[0]?.taxRate ?? 0;
|
? article.prices[0]
|
||||||
const gross = net != null ? +(net * (1 + taxRate / 100)).toFixed(2) : null;
|
: null;
|
||||||
const ean = (article.eans && article.eans.length > 0) ? article.eans[0] : '';
|
const net = basePrice?.net != null && Number.isFinite(Number(basePrice.net))
|
||||||
|
? Number(basePrice.net)
|
||||||
|
: null;
|
||||||
|
const taxRate = basePrice?.taxRate != null && Number.isFinite(Number(basePrice.taxRate))
|
||||||
|
? Number(basePrice.taxRate)
|
||||||
|
: 0;
|
||||||
|
const gross = net != null
|
||||||
|
? Math.round((net * (1 + taxRate / 100) + Number.EPSILON) * 100) / 100
|
||||||
|
: null;
|
||||||
|
|
||||||
return str
|
return str
|
||||||
.replace(/\{\{(?:ARTICLE_NAME|article_name|name)\}\}/gi, article.name || '')
|
.replace(/\{\{name\}\}/gi, article?.name || '')
|
||||||
.replace(/\{\{SKU\}\}/gi, article.sku || '')
|
.replace(/\{\{price\}\}/gi, formatPrice(gross));
|
||||||
.replace(/\{\{(?:NET_PRICE|net_price)\}\}/gi, net != null ? net.toString() : '')
|
}
|
||||||
.replace(/\{\{(?:GROSS_PRICE|gross_price)\}\}/gi, gross != null ? gross.toString() : '')
|
|
||||||
.replace(/\{\{(?:PRICE|price)\}\}/gi, gross != null ? gross.toString() : (net != null ? net.toString() : ''))
|
export function interpolateObjects(objects, article) {
|
||||||
.replace(/\{\{(?:EAN|ean)\}\}/gi, ean);
|
if (!Array.isArray(objects)) return [];
|
||||||
|
const ean = Array.isArray(article?.eans) && article.eans.length > 0 ? String(article.eans[0]) : '';
|
||||||
|
|
||||||
|
return objects.map((obj) => {
|
||||||
|
const cloned = { ...obj };
|
||||||
|
if (typeof cloned.text === 'string') {
|
||||||
|
cloned.text = interpolateString(cloned.text, article);
|
||||||
|
}
|
||||||
|
if (cloned.type === 'barcode' || typeof cloned.code === 'string') {
|
||||||
|
const rawCode = String(cloned.code ?? '').trim();
|
||||||
|
if (rawCode === '0' || /^0+$/.test(rawCode)) {
|
||||||
|
if (ean) {
|
||||||
|
cloned.code = ean;
|
||||||
|
}
|
||||||
|
} else if (typeof cloned.code === 'string') {
|
||||||
|
cloned.code = interpolateString(cloned.code, article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cloned;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export function register(app, basePath = '') {
|
|||||||
const parsed = parseSku(req.params.sku);
|
const parsed = parseSku(req.params.sku);
|
||||||
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
||||||
|
|
||||||
if (req.body?.sku || req.body?.childSku) {
|
if (req.body?.sku) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'sku is not allowed when creating a child article; omit sku to auto-generate or use PUT to attach an existing article',
|
error: 'sku is not allowed when creating a child article; omit sku to auto-generate or use PUT to attach an existing article',
|
||||||
});
|
});
|
||||||
@@ -282,9 +282,7 @@ export function register(app, basePath = '') {
|
|||||||
const parsed = parseSku(req.params.sku);
|
const parsed = parseSku(req.params.sku);
|
||||||
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
if (parsed.error) return res.status(400).json({ error: parsed.error });
|
||||||
|
|
||||||
const childSku = typeof req.body?.sku === 'string' && req.body.sku.trim()
|
const childSku = typeof req.body?.sku === 'string' ? req.body.sku.trim() : '';
|
||||||
? req.body.sku.trim()
|
|
||||||
: (typeof req.body?.childSku === 'string' && req.body.childSku.trim() ? req.body.childSku.trim() : '');
|
|
||||||
if (!childSku) {
|
if (!childSku) {
|
||||||
return res.status(400).json({ error: 'sku is required' });
|
return res.status(400).json({ error: 'sku is required' });
|
||||||
}
|
}
|
||||||
@@ -354,7 +352,7 @@ export function register(app, basePath = '') {
|
|||||||
const parsedChild = parseSku(req.params.childSku);
|
const parsedChild = parseSku(req.params.childSku);
|
||||||
if (parsedChild.error) return res.status(400).json({ error: 'childSku is required' });
|
if (parsedChild.error) return res.status(400).json({ error: 'childSku is required' });
|
||||||
|
|
||||||
const deleteArticle = req.query.deleteArticle === 'true' || req.body?.deleteArticle === true;
|
const deleteArticle = req.query.deleteArticle === 'true';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db.removeChild(req.user.id, parsed.sku, parsedChild.sku, {
|
const result = await db.removeChild(req.user.id, parsed.sku, parsedChild.sku, {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: skuParsed.error });
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const propertiesInput = req.body?.properties ?? req.body;
|
const propertiesInput = req.body?.properties;
|
||||||
if (!Array.isArray(propertiesInput)) {
|
if (!Array.isArray(propertiesInput)) {
|
||||||
return res.status(400).json({ error: 'properties array required' });
|
return res.status(400).json({ error: 'properties array required' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,12 +252,12 @@ export function register(app, basePath = '') {
|
|||||||
// #region POST /backup/:id/vfs/recover
|
// #region POST /backup/:id/vfs/recover
|
||||||
// MARK: handles POST /backup/:id/vfs/recover to restore a VFS file from a backup
|
// MARK: handles POST /backup/:id/vfs/recover to restore a VFS file from a backup
|
||||||
const handleRecover = async (req, res) => {
|
const handleRecover = async (req, res) => {
|
||||||
const filePath = req.params.path || req.body?.path || req.body?.file || req.body?.filename || req.query?.path;
|
const filePath = req.body?.path;
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
return res.status(400).json({ error: 'File path is required' });
|
return res.status(400).json({ error: 'File path is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestedCompanyId = req.body?.companyId ?? req.query?.companyId;
|
const requestedCompanyId = req.body?.companyId;
|
||||||
const authorized = await resolveAuthorizedCompany(req, requestedCompanyId);
|
const authorized = await resolveAuthorizedCompany(req, requestedCompanyId);
|
||||||
if (authorized.error) {
|
if (authorized.error) {
|
||||||
return res.status(authorized.status).json({ error: authorized.error });
|
return res.status(authorized.status).json({ error: authorized.error });
|
||||||
|
|||||||
@@ -384,8 +384,6 @@ export function register(app, basePath = '') {
|
|||||||
let includeChildren = true;
|
let includeChildren = true;
|
||||||
if (req.query.includeChildren !== undefined) {
|
if (req.query.includeChildren !== undefined) {
|
||||||
includeChildren = req.query.includeChildren !== 'false' && req.query.includeChildren !== '0';
|
includeChildren = req.query.includeChildren !== 'false' && req.query.includeChildren !== '0';
|
||||||
} else if (req.query.variants !== undefined) {
|
|
||||||
includeChildren = req.query.variants !== 'false' && req.query.variants !== '0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -395,7 +393,7 @@ export function register(app, basePath = '') {
|
|||||||
.map((a) => ({
|
.map((a) => ({
|
||||||
...a,
|
...a,
|
||||||
childCount: Number(a.childCount ?? 0),
|
childCount: Number(a.childCount ?? 0),
|
||||||
isParent: Boolean(a.isParent ?? a.is_parent),
|
isParent: Boolean(a.isParent),
|
||||||
smallThumbW: a.smallThumbW ?? null,
|
smallThumbW: a.smallThumbW ?? null,
|
||||||
smallThumbH: a.smallThumbH ?? null,
|
smallThumbH: a.smallThumbH ?? null,
|
||||||
smallThumbBinary: a.smallThumbBinary ?? null,
|
smallThumbBinary: a.smallThumbBinary ?? null,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: idParsed.error });
|
return res.status(400).json({ error: idParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const propertiesInput = req.body?.properties ?? req.body;
|
const propertiesInput = req.body?.properties;
|
||||||
if (!Array.isArray(propertiesInput)) {
|
if (!Array.isArray(propertiesInput)) {
|
||||||
return res.status(400).json({ error: 'properties array required' });
|
return res.status(400).json({ error: 'properties array required' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { pngToZpl } from '../../services/zplService.js';
|
|||||||
import { notifyAgentJob } from '../agent/index.js';
|
import { notifyAgentJob } from '../agent/index.js';
|
||||||
import { emit } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
import { applyZplCopies } from './zpljob.js';
|
import { applyZplCopies } from './zpljob.js';
|
||||||
import { interpolateString } from './article/shared.js';
|
import { interpolateString, interpolateObjects } from './article/shared.js';
|
||||||
|
|
||||||
// #region Helpers
|
// #region Helpers
|
||||||
|
|
||||||
@@ -590,16 +590,15 @@ export function register(app, basePath = '') {
|
|||||||
// Resolve sample article for placeholder interpolation
|
// Resolve sample article for placeholder interpolation
|
||||||
let article = null;
|
let article = null;
|
||||||
if (req.body?.sku) {
|
if (req.body?.sku) {
|
||||||
try {
|
const skuStr = String(req.body.sku).trim();
|
||||||
const result = await articleDb.getBySku(req.user.id, req.body.sku);
|
const result = await articleDb.getBySku(req.user.id, skuStr);
|
||||||
article = result?.article;
|
if (!result?.article) {
|
||||||
} catch {
|
return res.status(404).json({ error: 'Article not found' });
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
}
|
article = result.article;
|
||||||
if (!article) {
|
} else {
|
||||||
article = {
|
article = {
|
||||||
name: req.body?.name || template?.name || 'Musterartikel',
|
name: template?.name || 'Musterartikel',
|
||||||
sku: 'TEST-12345',
|
sku: 'TEST-12345',
|
||||||
prices: [{ net: 19.99, taxRate: 19 }],
|
prices: [{ net: 19.99, taxRate: 19 }],
|
||||||
eans: ['4012345678901'],
|
eans: ['4012345678901'],
|
||||||
@@ -625,16 +624,7 @@ export function register(app, basePath = '') {
|
|||||||
objects = [];
|
objects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const interpolatedObjects = objects.map((obj) => {
|
const interpolatedObjects = interpolateObjects(objects, article);
|
||||||
const cloned = { ...obj };
|
|
||||||
if (typeof cloned.text === 'string') {
|
|
||||||
cloned.text = interpolateString(cloned.text, article);
|
|
||||||
}
|
|
||||||
if (typeof cloned.code === 'string') {
|
|
||||||
cloned.code = interpolateString(cloned.code, article);
|
|
||||||
}
|
|
||||||
return cloned;
|
|
||||||
});
|
|
||||||
|
|
||||||
const printPayload = await renderLabel({
|
const printPayload = await renderLabel({
|
||||||
width: width || 448,
|
width: width || 448,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const manifestarticleId = Number(req.params.manifestarticleId);
|
const manifestarticleId = Number(req.params.manifestarticleId);
|
||||||
const transactionType = req.query.transaction_type ?? req.query.transactionType;
|
const transactionType = req.query.transaction_type;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ledger = await db.listByManifestarticleId(
|
const ledger = await db.listByManifestarticleId(
|
||||||
@@ -186,7 +186,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sku = req.params.sku;
|
const sku = req.params.sku;
|
||||||
const transactionType = req.query.transaction_type ?? req.query.transactionType;
|
const transactionType = req.query.transaction_type;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ledger = await db.listBySku(req.user.id, sku, transactionType);
|
const ledger = await db.listBySku(req.user.id, sku, transactionType);
|
||||||
@@ -254,9 +254,9 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: selected.error });
|
return res.status(400).json({ error: selected.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawManifestarticleId = req.query.manifestarticle_id ?? req.query.manifestarticleId;
|
const rawManifestarticleId = req.query.manifestarticle_id;
|
||||||
const sku = req.query.sku;
|
const sku = req.query.sku;
|
||||||
const transactionType = req.query.transaction_type ?? req.query.transactionType;
|
const transactionType = req.query.transaction_type;
|
||||||
|
|
||||||
if (rawManifestarticleId == null && (sku == null || !String(sku).trim())) {
|
if (rawManifestarticleId == null && (sku == null || !String(sku).trim())) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|||||||
@@ -190,12 +190,8 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pairingpin = String(randomInt(100000, 999999));
|
const pairingpin = String(randomInt(100000, 999999));
|
||||||
const rootCategoryId = req.body.rootCategoryId !== undefined
|
const rootCategoryId = req.body.rootCategoryId;
|
||||||
? req.body.rootCategoryId
|
const syncRootArticles = req.body.syncRootArticles;
|
||||||
: req.body.root_category_id;
|
|
||||||
const syncRootArticles = req.body.syncRootArticles !== undefined
|
|
||||||
? req.body.syncRootArticles
|
|
||||||
: req.body.sync_root_articles;
|
|
||||||
const sort = req.body.sort !== undefined ? Number(req.body.sort) : undefined;
|
const sort = req.body.sort !== undefined ? Number(req.body.sort) : undefined;
|
||||||
if (sort !== undefined && !Number.isInteger(sort)) {
|
if (sort !== undefined && !Number.isInteger(sort)) {
|
||||||
return res.status(400).json({ error: 'sort must be an integer' });
|
return res.status(400).json({ error: 'sort must be an integer' });
|
||||||
@@ -319,14 +315,12 @@ export function register(app, basePath = '') {
|
|||||||
payload.name = req.body.name.trim();
|
payload.name = req.body.name.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.rootCategoryId !== undefined || req.body.root_category_id !== undefined) {
|
if (req.body.rootCategoryId !== undefined) {
|
||||||
const rawCat = req.body.rootCategoryId !== undefined ? req.body.rootCategoryId : req.body.root_category_id;
|
payload.rootCategoryId = req.body.rootCategoryId != null ? Number(req.body.rootCategoryId) : null;
|
||||||
payload.rootCategoryId = rawCat != null ? Number(rawCat) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.syncRootArticles !== undefined || req.body.sync_root_articles !== undefined) {
|
if (req.body.syncRootArticles !== undefined) {
|
||||||
const rawSync = req.body.syncRootArticles !== undefined ? req.body.syncRootArticles : req.body.sync_root_articles;
|
payload.syncRootArticles = Boolean(req.body.syncRootArticles);
|
||||||
payload.syncRootArticles = Boolean(rawSync);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.sort !== undefined) {
|
if (req.body.sort !== undefined) {
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: selected.error });
|
return res.status(400).json({ error: selected.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawApId = req.body?.agentprinterId ?? req.body?.agentprinterid;
|
const rawApId = req.body?.agentprinterId;
|
||||||
const parsedApId = parsePositiveInt(rawApId, 'agentprinterId');
|
const parsedApId = parsePositiveInt(rawApId, 'agentprinterId');
|
||||||
if (parsedApId.error) {
|
if (parsedApId.error) {
|
||||||
return res.status(400).json({ error: parsedApId.error });
|
return res.status(400).json({ error: parsedApId.error });
|
||||||
@@ -302,7 +302,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rawApId = req.body?.agentprinterId ?? req.query?.agentprinterId;
|
const rawApId = req.body?.agentprinterId;
|
||||||
const agentprinterId = rawApId != null ? Number(rawApId) : undefined;
|
const agentprinterId = rawApId != null ? Number(rawApId) : undefined;
|
||||||
const allJobs = await printerjobDb.list(req.user.id, { agentprinterId });
|
const allJobs = await printerjobDb.list(req.user.id, { agentprinterId });
|
||||||
const cancellable = allJobs.filter((j) => j.state === 'pending' || j.state === 'processing');
|
const cancellable = allJobs.filter((j) => j.state === 'pending' || j.state === 'processing');
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: idParsed.error });
|
return res.status(400).json({ error: idParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const skus = parseSkus(req.query.skus ?? req.query.sku);
|
const skus = parseSkus(req.query.skus);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const articles = await db.listArticlesBySkus(req.user.id, idParsed.id, skus);
|
const articles = await db.listArticlesBySkus(req.user.id, idParsed.id, skus);
|
||||||
@@ -540,7 +540,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: idParsed.error });
|
return res.status(400).json({ error: idParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const skus = parseSkus(req.body?.skus ?? req.body?.sku);
|
const skus = parseSkus(req.body?.skus);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const articles = await db.listArticlesBySkus(req.user.id, idParsed.id, skus);
|
const articles = await db.listArticlesBySkus(req.user.id, idParsed.id, skus);
|
||||||
|
|||||||
@@ -685,7 +685,7 @@ export function register(app, basePath = '') {
|
|||||||
if (selected.error) {
|
if (selected.error) {
|
||||||
return res.status(400).json({ error: selected.error });
|
return res.status(400).json({ error: selected.error });
|
||||||
}
|
}
|
||||||
const raw = req.body?.path || req.body?.dir || req.body?.name;
|
const raw = req.body?.path;
|
||||||
const parsed = parseVfsRef(raw);
|
const parsed = parseVfsRef(raw);
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
return res.status(400).json({ error: parsed.error });
|
return res.status(400).json({ error: parsed.error });
|
||||||
@@ -787,8 +787,8 @@ export function register(app, basePath = '') {
|
|||||||
if (selected.error) {
|
if (selected.error) {
|
||||||
return res.status(400).json({ error: selected.error });
|
return res.status(400).json({ error: selected.error });
|
||||||
}
|
}
|
||||||
const rawOld = req.body?.oldPath || req.body?.from || req.body?.source;
|
const rawOld = req.body?.oldPath;
|
||||||
const rawNew = req.body?.newPath || req.body?.to || req.body?.target;
|
const rawNew = req.body?.newPath;
|
||||||
const parsedOld = parseVfsRef(rawOld);
|
const parsedOld = parseVfsRef(rawOld);
|
||||||
if (parsedOld.error) {
|
if (parsedOld.error) {
|
||||||
return res.status(400).json({ error: `oldPath: ${parsedOld.error}` });
|
return res.status(400).json({ error: `oldPath: ${parsedOld.error}` });
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function buildStudioApp() {
|
|||||||
// 5. Insert row
|
// 5. Insert row
|
||||||
app.post('/api/tables/:table/row', async (req, res) => {
|
app.post('/api/tables/:table/row', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await dbHelper.insertRow(req.params.table, req.body.row || req.body);
|
const result = await dbHelper.insertRow(req.params.table, req.body.row);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({ error: err.message });
|
res.status(400).json({ error: err.message });
|
||||||
@@ -175,7 +175,3 @@ export async function stopBackendMonitor() {
|
|||||||
});
|
});
|
||||||
monitorLog.info('Backend Monitor Interface stopped');
|
monitorLog.info('Backend Monitor Interface stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aliases for compatibility
|
|
||||||
export const startSqlStudio = startBackendMonitor;
|
|
||||||
export const stopSqlStudio = stopBackendMonitor;
|
|
||||||
|
|||||||
@@ -116,25 +116,21 @@ async function snapshotSqliteDatabase(targetFile) {
|
|||||||
fs.unlinkSync(targetFile);
|
fs.unlinkSync(targetFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const provider = await getProvider();
|
||||||
const provider = await getProvider();
|
if (provider?.db && typeof provider.db.backup === 'function') {
|
||||||
if (provider?.db && typeof provider.db.backup === 'function') {
|
await provider.db.backup(targetFile);
|
||||||
await provider.db.backup(targetFile);
|
const stats = fs.statSync(targetFile);
|
||||||
const stats = fs.statSync(targetFile);
|
return { success: true, sizeBytes: stats.size };
|
||||||
return { success: true, sizeBytes: stats.size };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider?.query) {
|
|
||||||
const escapedPath = targetFile.replace(/'/g, "''");
|
|
||||||
await provider.query(`VACUUM INTO '${escapedPath}'`);
|
|
||||||
const stats = fs.statSync(targetFile);
|
|
||||||
return { success: true, sizeBytes: stats.size };
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
backupLog.warn(`Online SQLite backup method failed (${err.message}), falling back to direct copy`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct file fallback
|
if (provider?.query) {
|
||||||
|
const escapedPath = targetFile.replace(/'/g, "''");
|
||||||
|
await provider.query(`VACUUM INTO '${escapedPath}'`);
|
||||||
|
const stats = fs.statSync(targetFile);
|
||||||
|
return { success: true, sizeBytes: stats.size };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct file copy when provider is offline / not connected
|
||||||
const { sqlitePath } = resolveBackupPaths();
|
const { sqlitePath } = resolveBackupPaths();
|
||||||
if (fs.existsSync(sqlitePath)) {
|
if (fs.existsSync(sqlitePath)) {
|
||||||
fs.copyFileSync(sqlitePath, targetFile);
|
fs.copyFileSync(sqlitePath, targetFile);
|
||||||
|
|||||||
@@ -180,19 +180,17 @@ export function extractLiveStatus(live) {
|
|||||||
const isOffline = live.status === 'offline' || live.unreachable === true || errors.unreachable === true;
|
const isOffline = live.status === 'offline' || live.unreachable === true || errors.unreachable === true;
|
||||||
const online = !isOffline;
|
const online = !isOffline;
|
||||||
|
|
||||||
const open = Boolean(live.open ?? live.headOpen ?? errors.headOpen ?? false);
|
const open = Boolean(live.open || errors.headOpen);
|
||||||
const pause = Boolean(live.pause ?? live.paused ?? errors.pause ?? false);
|
const pause = Boolean(live.pause || errors.pause);
|
||||||
|
|
||||||
const hasOtherError = Boolean(
|
const hasOtherError = Boolean(
|
||||||
live.error ||
|
live.error ||
|
||||||
errors.other ||
|
errors.other ||
|
||||||
errors.error ||
|
|
||||||
Object.entries(errors).some(([key, val]) => !['headOpen', 'pause', 'unreachable'].includes(key) && Boolean(val))
|
Object.entries(errors).some(([key, val]) => !['headOpen', 'pause', 'unreachable'].includes(key) && Boolean(val))
|
||||||
);
|
);
|
||||||
|
|
||||||
const rawLabelLength = live.labelLength ?? live.labellength;
|
const labelLength = live.labelLength != null && Number.isFinite(Number(live.labelLength))
|
||||||
const labelLength = rawLabelLength != null && Number.isFinite(Number(rawLabelLength))
|
? Number(live.labelLength)
|
||||||
? Number(rawLabelLength)
|
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { optimizeImage } from 'wasm-image-optimization';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { getImageByHash } from '../db/pic.js';
|
import { getImageByHash } from '../db/pic.js';
|
||||||
import { prepareVfs, syncVfsToDb, extractFullHash, isReadOnlyDatabasePath } from './vfs.js';
|
import { prepareVfs, extractFullHash, isReadOnlyDatabasePath } from './vfs.js';
|
||||||
import { resolveOpenRouterKey, resolveImageModel } from './llm-key.js';
|
import { resolveOpenRouterKey, resolveImageModel } from './llm-key.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
@@ -482,11 +482,7 @@ export function createEditImageTool({ googleAuthId, model: fallbackModel }) {
|
|||||||
fs.mkdirSync(path.dirname(absTarget), { recursive: true });
|
fs.mkdirSync(path.dirname(absTarget), { recursive: true });
|
||||||
fs.writeFileSync(absTarget, finalBuffer);
|
fs.writeFileSync(absTarget, finalBuffer);
|
||||||
|
|
||||||
try {
|
|
||||||
await syncVfsToDb({ googleAuthId, vfsRoot });
|
|
||||||
} catch (syncErr) {
|
|
||||||
console.warn('[edit_image] warning: failed to sync output image to modelvfs db:', syncErr.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const outFinalRel = path.relative(vfsRoot, absTarget).replace(/\\/g, '/');
|
const outFinalRel = path.relative(vfsRoot, absTarget).replace(/\\/g, '/');
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import jwt from 'jsonwebtoken';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
import { createVfs, prepareVfs, syncVfsToDb } from './vfs.js';
|
import { createVfs, prepareVfs } from './vfs.js';
|
||||||
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS = 20_000;
|
const DEFAULT_TIMEOUT_MS = 20_000;
|
||||||
@@ -367,19 +367,14 @@ export function createJavascriptExecuteTool({ googleAuthId }) {
|
|||||||
const isApplet = hasApplet;
|
const isApplet = hasApplet;
|
||||||
const applet = isApplet ? readApplet(vfsRoot, appletvfsfile) : null;
|
const applet = isApplet ? readApplet(vfsRoot, appletvfsfile) : null;
|
||||||
const source = isApplet ? applet.source : code;
|
const source = isApplet ? applet.source : code;
|
||||||
let report;
|
const report = await runInWorker({
|
||||||
try {
|
code: source,
|
||||||
report = await runInWorker({
|
timeoutMs: hardTimeout,
|
||||||
code: source,
|
vfsRoot,
|
||||||
timeoutMs: hardTimeout,
|
googleAuthId,
|
||||||
vfsRoot,
|
appletInput: isApplet ? (applet_input ?? {}) : undefined,
|
||||||
googleAuthId,
|
appletFile: isApplet ? appletvfsfile.trim() : undefined,
|
||||||
appletInput: isApplet ? (applet_input ?? {}) : undefined,
|
});
|
||||||
appletFile: isApplet ? appletvfsfile.trim() : undefined,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
await syncVfsToDb({ googleAuthId, vfsRoot });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (report.timedOut) {
|
if (report.timedOut) {
|
||||||
throw new ToolError(
|
throw new ToolError(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { isReadOnlyDatabasePath } from './vfs.js';
|
import { isReadOnlyDatabasePath } from './vfs.js';
|
||||||
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs } from './vfs.js';
|
||||||
import { TOOL_DESCRIPTIONS, PATCH_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, PATCH_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
import { getStore } from '../db/store.js';
|
import { getStore } from '../db/store.js';
|
||||||
|
|
||||||
@@ -262,17 +262,7 @@ export function createPatchTool({ googleAuthId }) {
|
|||||||
if (isSkill) touchedSkills.add(clean);
|
if (isSkill) touchedSkills.add(clean);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applied.length > 0) {
|
|
||||||
try {
|
|
||||||
await syncVfsToDb({ googleAuthId, vfsRoot });
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[patch] failed to sync VFS to db:', err.message);
|
|
||||||
throw new ToolError(
|
|
||||||
`Patch applied to disk but could not be persisted: ${err.message}. ` +
|
|
||||||
`Files changed: ${applied.join(', ')}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { pdfToText, splitPdfPages, pdfToPng } from 'pdfconvert-wasm';
|
import { pdfToText, splitPdfPages, pdfToPng } from 'pdfconvert-wasm';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs } from './vfs.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const IMAGE_FOLDER = 'images';
|
const IMAGE_FOLDER = 'images';
|
||||||
@@ -186,13 +186,7 @@ export function createPdfReadTool({ googleAuthId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedImages.length > 0) {
|
|
||||||
try {
|
|
||||||
await syncVfsToDb({ googleAuthId, vfsRoot });
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[pdf_read] failed to sync rendered images to VFS DB:', err?.message || err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.images = savedImages;
|
result.images = savedImages;
|
||||||
if (!result.totalPages) result.totalPages = images.length;
|
if (!result.totalPages) result.totalPages = images.length;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { optimizeImage } from 'wasm-image-optimization';
|
import { optimizeImage } from 'wasm-image-optimization';
|
||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import * as dbCompanySetting from '../db/companysetting.js';
|
import * as dbCompanySetting from '../db/companysetting.js';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs } from './vfs.js';
|
||||||
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const SERPER_ENDPOINT = 'https://google.serper.dev/images';
|
const SERPER_ENDPOINT = 'https://google.serper.dev/images';
|
||||||
@@ -249,16 +249,7 @@ export function createSearchImagesTool({ googleAuthId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.length > 0) {
|
|
||||||
try {
|
|
||||||
await syncVfsToDb({ googleAuthId, vfsRoot });
|
|
||||||
} catch (err) {
|
|
||||||
// Non-fatal: the files are on disk and will be picked up by the next
|
|
||||||
// javascript_execute / patch sync. Losing the images because a DB write
|
|
||||||
// failed would be a worse outcome than a slightly stale store.
|
|
||||||
console.warn('[search_images] failed to persist candidates:', err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
query: term,
|
query: term,
|
||||||
|
|||||||
@@ -257,14 +257,6 @@ export async function prepareVfs({ googleAuthId }) {
|
|||||||
return vfsRoot;
|
return vfsRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Purely disk-based VFS; no database syncing needed. Kept as no-op for backward compatibility.
|
|
||||||
* @param {{ googleAuthId?: number, vfsRoot?: string }} [ctx]
|
|
||||||
*/
|
|
||||||
export async function syncVfsToDb() {
|
|
||||||
// Purely disk-based VFS; no database syncing.
|
|
||||||
}
|
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Sandboxed VFS Factory
|
// #region Sandboxed VFS Factory
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const skeleCache = new Map();
|
|||||||
export async function formatAttachment(file) {
|
export async function formatAttachment(file) {
|
||||||
if (!file || typeof file !== 'object') return file;
|
if (!file || typeof file !== 'object') return file;
|
||||||
|
|
||||||
let skeledash = file.skeledash || file.skelehash || file.meta?.skeledash || file.meta?.skelehash || null;
|
let skeledash = file.skeledash || file.meta?.skeledash || null;
|
||||||
|
|
||||||
if (!skeledash && file.hash && skeleCache.has(file.hash)) {
|
if (!skeledash && file.hash && skeleCache.has(file.hash)) {
|
||||||
skeledash = skeleCache.get(file.hash);
|
skeledash = skeleCache.get(file.hash);
|
||||||
@@ -36,7 +36,6 @@ export async function formatAttachment(file) {
|
|||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
skeledash,
|
skeledash,
|
||||||
skelehash: skeledash,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user