@unchainedshop/api 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -0
- package/lib/chat/generateImageHandler.d.ts +7 -1
- package/lib/chat/generateImageHandler.js +4 -5
- package/lib/express/chatHandler.js +4 -4
- package/lib/express/createERCMetadataMiddleware.js +1 -1
- package/lib/express/index.js +23 -9
- package/lib/fastify/chatHandler.js +4 -4
- package/lib/fastify/ercMetadataHandler.js +1 -1
- package/lib/mcp/tools/assortment/handlers/addAssortmentFilter.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/addAssortmentLink.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/addAssortmentMedia.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/addAssortmentProduct.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/createAssortment.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/getAssortment.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/getAssortmentChildren.d.ts +0 -2
- package/lib/mcp/tools/assortment/handlers/getAssortmentFilters.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/getAssortmentLinks.d.ts +0 -2
- package/lib/mcp/tools/assortment/handlers/getAssortmentMedia.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/getAssortmentProducts.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/index.d.ts +0 -2
- package/lib/mcp/tools/assortment/handlers/index.js +0 -2
- package/lib/mcp/tools/assortment/handlers/listAssortments.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/reorderAssortmentLinks.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/searchAssortmentProducts.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/updateAssortment.d.ts +0 -1
- package/lib/mcp/tools/assortment/handlers/updateAssortmentStatus.d.ts +0 -1
- package/lib/mcp/tools/assortment/schemas.d.ts +0 -5
- package/lib/mcp/tools/assortment/schemas.js +0 -4
- package/lib/mcp/tools/order/handlers/confirmOrder.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/deliverOrder.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/getOrder.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/getTopCustomers.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/getTopCustomers.js +1 -1
- package/lib/mcp/tools/order/handlers/getTopProducts.js +1 -1
- package/lib/mcp/tools/order/handlers/getUserCart.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/listOrders.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/payOrder.d.ts +1 -1
- package/lib/mcp/tools/order/handlers/rejectOrder.d.ts +1 -1
- package/lib/mcp/tools/quotation/handlers/getQuotation.d.ts +2 -2
- package/lib/mcp/tools/quotation/handlers/listQuotations.d.ts +2 -2
- package/lib/mcp/tools/quotation/handlers/makeQuotationProposal.d.ts +2 -2
- package/lib/mcp/tools/quotation/handlers/rejectQuotation.d.ts +2 -2
- package/lib/mcp/tools/quotation/handlers/rejectQuotation.js +1 -1
- package/lib/mcp/tools/quotation/handlers/verifyQuotation.d.ts +2 -2
- package/lib/mcp/tools/quotation/index.js +1 -1
- package/lib/mcp/tools/quotation/schemas.d.ts +5 -5
- package/lib/mcp/tools/quotation/schemas.js +3 -3
- package/lib/mcp/tools/system/schemas.d.ts +6 -6
- package/lib/mcp/tools/system/schemas.js +14 -7
- package/lib/mcp/tools/users/handlers/addUserEmail.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/createUser.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/enrollUser.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/getCurrentUser.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/getUser.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/getUserOrders.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/getUserQuotations.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/listUsers.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/setUserTags.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/setUserUsername.d.ts +1 -1
- package/lib/mcp/tools/users/handlers/updateUser.d.ts +1 -1
- package/lib/mcp/utils/getNormalizedAssortmentDetails.d.ts +0 -1
- package/lib/mcp/utils/getNormalizedOrderDetails.d.ts +1 -1
- package/lib/mcp/utils/getNormalizedQuotationDetails.d.ts +2 -2
- package/lib/mcp/utils/getNormalizedUserDetails.d.ts +1 -1
- package/lib/mongo-store.d.ts +9 -5
- package/lib/mongo-store.js +31 -25
- package/lib/resolvers/mutations/accounts/forgotPassword.js +4 -4
- package/lib/resolvers/mutations/index.d.ts +0 -1
- package/lib/resolvers/mutations/index.js +0 -2
- package/lib/resolvers/mutations/quotations/rejectQuotation.js +1 -1
- package/lib/resolvers/mutations/users/removeUser.d.ts +1 -1
- package/lib/resolvers/mutations/users/removeUser.js +1 -2
- package/lib/resolvers/type/order/order-statistics-types.js +2 -2
- package/lib/schema/mutation.js +1 -7
- package/lib/schema/types/assortment.js +0 -1
- package/lib/schema/types/order/order.js +2 -2
- package/lib/schema/types/quotation.js +2 -2
- package/package.json +13 -12
- package/lib/mcp/tools/assortment/handlers/setAssortmentBase.d.ts +0 -28
- package/lib/mcp/tools/assortment/handlers/setAssortmentBase.js +0 -11
- package/lib/resolvers/mutations/assortments/setBaseAssortment.d.ts +0 -4
- package/lib/resolvers/mutations/assortments/setBaseAssortment.js +0 -11
package/README.md
CHANGED
|
@@ -149,6 +149,210 @@ import { createMCPServer } from '@unchainedshop/api/mcp';
|
|
|
149
149
|
const mcpServer = createMCPServer(unchainedCore);
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
## Security
|
|
153
|
+
|
|
154
|
+
The API layer implements comprehensive security controls.
|
|
155
|
+
|
|
156
|
+
### Access Control
|
|
157
|
+
|
|
158
|
+
- **128+ permission actions** covering all API operations
|
|
159
|
+
- **Role-Based Access Control (RBAC)** with built-in and custom roles
|
|
160
|
+
- **ACL enforcement** on all GraphQL mutations
|
|
161
|
+
- **Ownership validation** ensuring users can only access their resources
|
|
162
|
+
|
|
163
|
+
### Session Security
|
|
164
|
+
|
|
165
|
+
| Variable | Purpose | Default |
|
|
166
|
+
|----------|---------|---------|
|
|
167
|
+
| `UNCHAINED_TOKEN_SECRET` | Session encryption (min 32 chars) | Required |
|
|
168
|
+
| `UNCHAINED_COOKIE_NAME` | Cookie name | `unchained_token` |
|
|
169
|
+
| `UNCHAINED_COOKIE_SAMESITE` | SameSite attribute | `none` |
|
|
170
|
+
| `UNCHAINED_COOKIE_INSECURE` | Disable secure flag | `false` |
|
|
171
|
+
|
|
172
|
+
Cookies are `httpOnly` and `secure` by default.
|
|
173
|
+
|
|
174
|
+
### Error Handling
|
|
175
|
+
|
|
176
|
+
Errors are designed to prevent information leakage:
|
|
177
|
+
- Generic authentication error messages
|
|
178
|
+
- No distinction between "invalid" vs "expired" tokens
|
|
179
|
+
- Permission errors don't reveal action details
|
|
180
|
+
|
|
181
|
+
### CORS Configuration
|
|
182
|
+
|
|
183
|
+
CORS behavior depends on `NODE_ENV`:
|
|
184
|
+
|
|
185
|
+
| Environment | Default Behavior |
|
|
186
|
+
|-------------|------------------|
|
|
187
|
+
| `development` | Permissive CORS (reflects any origin) + trust proxy headers |
|
|
188
|
+
| `production` | No CORS headers (reverse proxy should handle it) |
|
|
189
|
+
|
|
190
|
+
#### Environment Variable
|
|
191
|
+
|
|
192
|
+
| Variable | Purpose | Default |
|
|
193
|
+
|----------|---------|---------|
|
|
194
|
+
| `UNCHAINED_CORS_ORIGINS` | Allowed CORS origins | Auto (see above) |
|
|
195
|
+
|
|
196
|
+
#### Programmatic Configuration
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Auto behavior (recommended) - permissive in dev, none in prod
|
|
200
|
+
connect(app, { graphqlHandler, db, unchainedAPI });
|
|
201
|
+
|
|
202
|
+
// Explicit whitelist (production)
|
|
203
|
+
connect(app, { graphqlHandler, db, unchainedAPI }, {
|
|
204
|
+
corsOrigins: "https://shop.example.com,https://admin.example.com",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Force permissive (not recommended in production)
|
|
208
|
+
connect(app, { graphqlHandler, db, unchainedAPI }, {
|
|
209
|
+
corsOrigins: true,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Disable CORS entirely
|
|
213
|
+
connect(app, { graphqlHandler, db, unchainedAPI }, {
|
|
214
|
+
corsOrigins: false,
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Deployment Scenarios
|
|
219
|
+
|
|
220
|
+
#### Scenario 1: Direct TLS (No Reverse Proxy)
|
|
221
|
+
|
|
222
|
+
For simple deployments where the Node.js server handles TLS directly:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import express from 'express';
|
|
226
|
+
import https from 'https';
|
|
227
|
+
import fs from 'fs';
|
|
228
|
+
import { connect } from '@unchainedshop/api/express';
|
|
229
|
+
|
|
230
|
+
const app = express();
|
|
231
|
+
|
|
232
|
+
// Configure CORS for your frontend origins
|
|
233
|
+
connect(app, { graphqlHandler, db, unchainedAPI }, {
|
|
234
|
+
corsOrigins: "https://shop.example.com,https://admin.example.com",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Create HTTPS server with your certificates
|
|
238
|
+
https.createServer({
|
|
239
|
+
key: fs.readFileSync('/path/to/privkey.pem'),
|
|
240
|
+
cert: fs.readFileSync('/path/to/fullchain.pem'),
|
|
241
|
+
}, app).listen(443);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Environment:
|
|
245
|
+
```bash
|
|
246
|
+
NODE_ENV=production
|
|
247
|
+
UNCHAINED_CORS_ORIGINS=https://shop.example.com,https://admin.example.com
|
|
248
|
+
UNCHAINED_TOKEN_SECRET=your-32-char-secret-here
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Scenario 2: Behind Reverse Proxy (Recommended)
|
|
252
|
+
|
|
253
|
+
For production deployments behind nginx, Caddy, or a cloud load balancer:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import express from 'express';
|
|
257
|
+
import { connect } from '@unchainedshop/api/express';
|
|
258
|
+
|
|
259
|
+
const app = express();
|
|
260
|
+
|
|
261
|
+
// Trust the reverse proxy for client IP headers
|
|
262
|
+
app.set('trust proxy', 1);
|
|
263
|
+
|
|
264
|
+
// Let the proxy handle CORS, or configure here
|
|
265
|
+
connect(app, { graphqlHandler, db, unchainedAPI }, {
|
|
266
|
+
corsOrigins: "https://shop.example.com,https://admin.example.com",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
app.listen(4010); // Internal port, not exposed
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Nginx configuration:**
|
|
273
|
+
```nginx
|
|
274
|
+
server {
|
|
275
|
+
listen 443 ssl http2;
|
|
276
|
+
server_name api.example.com;
|
|
277
|
+
|
|
278
|
+
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
|
|
279
|
+
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
|
|
280
|
+
|
|
281
|
+
location / {
|
|
282
|
+
# Pass client IP to the app
|
|
283
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
284
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
285
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
286
|
+
proxy_set_header Host $host;
|
|
287
|
+
|
|
288
|
+
proxy_pass http://localhost:4010;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Caddy configuration:**
|
|
294
|
+
```caddyfile
|
|
295
|
+
api.example.com {
|
|
296
|
+
reverse_proxy localhost:4010
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Scenario 3: Proxy Handles CORS
|
|
301
|
+
|
|
302
|
+
For complex multi-origin setups, let the reverse proxy manage CORS:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// Don't set corsOrigins - let proxy handle it
|
|
306
|
+
app.set('trust proxy', 1);
|
|
307
|
+
connect(app, { graphqlHandler, db, unchainedAPI });
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Nginx with CORS:**
|
|
311
|
+
```nginx
|
|
312
|
+
location / {
|
|
313
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
314
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
315
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
316
|
+
|
|
317
|
+
# CORS whitelist
|
|
318
|
+
set $cors "";
|
|
319
|
+
if ($http_origin ~* "^https://(shop|admin)\.example\.com$") {
|
|
320
|
+
set $cors $http_origin;
|
|
321
|
+
}
|
|
322
|
+
add_header 'Access-Control-Allow-Origin' $cors always;
|
|
323
|
+
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
|
324
|
+
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
|
325
|
+
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
|
|
326
|
+
|
|
327
|
+
if ($request_method = 'OPTIONS') { return 204; }
|
|
328
|
+
|
|
329
|
+
proxy_pass http://localhost:4010;
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Trust Proxy
|
|
334
|
+
|
|
335
|
+
In development mode (`NODE_ENV=development`), trust proxy is automatically enabled.
|
|
336
|
+
|
|
337
|
+
In production, configure it on your Express/Fastify app before calling `connect()`:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// Express
|
|
341
|
+
app.set('trust proxy', 1); // Trust first hop
|
|
342
|
+
app.set('trust proxy', 'loopback'); // Trust loopback addresses
|
|
343
|
+
app.set('trust proxy', '10.0.0.0/8'); // Trust specific CIDR
|
|
344
|
+
|
|
345
|
+
// Fastify
|
|
346
|
+
const fastify = Fastify({ trustProxy: true });
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**SECURITY**: Only enable trust proxy if your reverse proxy:
|
|
350
|
+
1. Strips incoming `X-Real-IP` and `X-Forwarded-For` headers from clients
|
|
351
|
+
2. Sets `X-Real-IP` to the actual client IP
|
|
352
|
+
3. Is the only way to reach your API server
|
|
353
|
+
|
|
354
|
+
See [SECURITY.md](../../SECURITY.md) for complete security documentation including FIPS 140-3 mode.
|
|
355
|
+
|
|
152
356
|
## License
|
|
153
357
|
|
|
154
358
|
EUPL-1.2
|
|
@@ -2,5 +2,11 @@ import type * as aiTypes from 'ai';
|
|
|
2
2
|
declare const generateImageHandler: (req: any) => ({ model, uploadUrl, }: {
|
|
3
3
|
model: aiTypes.ImageModel;
|
|
4
4
|
uploadUrl?: string;
|
|
5
|
-
}) => aiTypes.Tool<
|
|
5
|
+
}) => aiTypes.Tool<{
|
|
6
|
+
prompt: string;
|
|
7
|
+
size?: "512x512" | "768x768" | "1024x1024" | "512x896" | "640x1120" | "768x1344" | "1024x1792" | "896x512" | "1120x640" | "1344x768" | "1792x1024" | undefined;
|
|
8
|
+
}, string | {
|
|
9
|
+
imageUrl: any;
|
|
10
|
+
prompt: string;
|
|
11
|
+
}>;
|
|
6
12
|
export default generateImageHandler;
|
|
@@ -2,12 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import mime from 'mime/lite';
|
|
3
3
|
import { createLogger } from '@unchainedshop/logger';
|
|
4
4
|
const logger = createLogger('unchained:api:mcp');
|
|
5
|
-
let
|
|
5
|
+
let generateImage;
|
|
6
6
|
let tool;
|
|
7
7
|
try {
|
|
8
8
|
const aiTools = await import('ai');
|
|
9
9
|
tool = aiTools.tool;
|
|
10
|
-
|
|
10
|
+
generateImage = aiTools.generateImage;
|
|
11
11
|
}
|
|
12
12
|
catch {
|
|
13
13
|
}
|
|
@@ -33,11 +33,10 @@ const inputSchema = z.object({
|
|
|
33
33
|
const generateImageHandler = (req) => ({ model, uploadUrl = `${process.env.ROOT_URL}/temp-upload`, }) => {
|
|
34
34
|
return tool({
|
|
35
35
|
description: 'Generate an image based on the prompt',
|
|
36
|
-
inputSchema
|
|
37
|
-
name: 'generate_image',
|
|
36
|
+
inputSchema,
|
|
38
37
|
execute: async ({ prompt, size = '1024x1024' }) => {
|
|
39
38
|
try {
|
|
40
|
-
const { image } = await
|
|
39
|
+
const { image } = await generateImage({
|
|
41
40
|
model,
|
|
42
41
|
prompt,
|
|
43
42
|
size: size,
|
|
@@ -21,7 +21,7 @@ try {
|
|
|
21
21
|
convertToModelMessages = aiTools.convertToModelMessages;
|
|
22
22
|
stepCountIs = aiTools.stepCountIs;
|
|
23
23
|
streamText = aiTools.streamText;
|
|
24
|
-
createMCPClient = mcpTools.
|
|
24
|
+
createMCPClient = mcpTools.createMCPClient;
|
|
25
25
|
}
|
|
26
26
|
catch {
|
|
27
27
|
logger.warn(`optional peer npm packages 'ai', '@ai-sdk/mcp' and '@modelcontextprotocol/sdk' not installed, chat will not work`);
|
|
@@ -76,7 +76,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
catch (e) {
|
|
79
|
-
logger.error(`Failed to read resource ${resource.uri}
|
|
79
|
+
logger.error(`Failed to read resource ${resource.uri}: ${e.message}`);
|
|
80
80
|
}
|
|
81
81
|
return null;
|
|
82
82
|
}));
|
|
@@ -85,7 +85,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
catch (e) {
|
|
88
|
-
logger.error(
|
|
88
|
+
logger.error(`Failed to fetch MCP resources: ${e.message}`);
|
|
89
89
|
}
|
|
90
90
|
const tools = {
|
|
91
91
|
...defaultUnchainedTools,
|
|
@@ -111,7 +111,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
111
111
|
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
|
-
const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
114
|
+
const normalizedMessages = await convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
115
115
|
if (normalizedMessages.length > 0) {
|
|
116
116
|
const lastIndex = normalizedMessages.length - 1;
|
|
117
117
|
normalizedMessages[lastIndex] = {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@unchainedshop/logger';
|
|
2
2
|
const logger = createLogger('unchained:erc-metadata');
|
|
3
|
-
const { ROOT_URL = 'http://localhost:4010' } = process.env;
|
|
4
3
|
const methodWrongHandler = (res) => {
|
|
5
4
|
logger.error('Method not supported, return 404');
|
|
6
5
|
res.status(404).end();
|
|
@@ -11,6 +10,7 @@ const ercMetadataMiddleware = async (req, res) => {
|
|
|
11
10
|
if (req.method !== 'GET') {
|
|
12
11
|
return methodWrongHandler(res);
|
|
13
12
|
}
|
|
13
|
+
const { ROOT_URL = 'http://localhost:4010' } = process.env;
|
|
14
14
|
const { services, modules, loaders, locale } = req.unchainedContext;
|
|
15
15
|
const url = new URL(req.url, ROOT_URL);
|
|
16
16
|
const pathname = url.pathname;
|
package/lib/express/index.js
CHANGED
|
@@ -64,19 +64,33 @@ const addContext = async function middlewareWithContext(req, res, next) {
|
|
|
64
64
|
const { user } = req;
|
|
65
65
|
if (!user)
|
|
66
66
|
return false;
|
|
67
|
+
const currentSessionId = req.sessionID;
|
|
68
|
+
const targetSessionId = sessionId || currentSessionId;
|
|
67
69
|
const tokenObject = {
|
|
68
|
-
_id:
|
|
70
|
+
_id: targetSessionId,
|
|
69
71
|
userId: user._id,
|
|
70
72
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
if (sessionId && sessionId !== currentSessionId) {
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
req.sessionStore.destroy(sessionId, (error) => {
|
|
76
|
+
if (error) {
|
|
77
|
+
return reject(error);
|
|
78
|
+
}
|
|
79
|
+
return resolve();
|
|
80
|
+
});
|
|
78
81
|
});
|
|
79
|
-
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await new Promise((resolve, reject) => {
|
|
85
|
+
req.logout((error, result) => {
|
|
86
|
+
if (error) {
|
|
87
|
+
return reject(error);
|
|
88
|
+
}
|
|
89
|
+
req.session.impersonatorId = null;
|
|
90
|
+
return resolve(result);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
80
94
|
await emit(API_EVENTS.API_LOGOUT, tokenObject);
|
|
81
95
|
return true;
|
|
82
96
|
};
|
|
@@ -20,7 +20,7 @@ try {
|
|
|
20
20
|
convertToModelMessages = aiTools.convertToModelMessages;
|
|
21
21
|
stepCountIs = aiTools.stepCountIs;
|
|
22
22
|
streamText = aiTools.streamText;
|
|
23
|
-
createMCPClient = mcpTools.
|
|
23
|
+
createMCPClient = mcpTools.createMCPClient;
|
|
24
24
|
}
|
|
25
25
|
catch {
|
|
26
26
|
logger.warn(`optional peer npm packages 'ai' and '@ai-sdk/mcp' not installed, chat will not work`);
|
|
@@ -73,7 +73,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
catch (e) {
|
|
76
|
-
logger.error(`Failed to read resource ${resource.uri}
|
|
76
|
+
logger.error(`Failed to read resource ${resource.uri}: ${e.message}`);
|
|
77
77
|
}
|
|
78
78
|
return null;
|
|
79
79
|
}));
|
|
@@ -82,7 +82,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
catch (e) {
|
|
85
|
-
logger.error(
|
|
85
|
+
logger.error(`Failed to fetch MCP resources: ${e.message}`);
|
|
86
86
|
}
|
|
87
87
|
const tools = {
|
|
88
88
|
...defaultUnchainedTools,
|
|
@@ -107,7 +107,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
|
|
|
107
107
|
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
108
108
|
},
|
|
109
109
|
};
|
|
110
|
-
const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
110
|
+
const normalizedMessages = await convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
111
111
|
if (normalizedMessages.length > 0) {
|
|
112
112
|
const lastIndex = normalizedMessages.length - 1;
|
|
113
113
|
normalizedMessages[lastIndex] = {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createLogger } from '@unchainedshop/logger';
|
|
2
2
|
import { ProductContractStandard } from '@unchainedshop/core-products';
|
|
3
3
|
const logger = createLogger('unchained:api:erc-metadata');
|
|
4
|
-
const { ROOT_URL = 'http://localhost:4010' } = process.env;
|
|
5
4
|
const notFoundHandler = (res) => {
|
|
6
5
|
logger.error('Method not supported, return 404');
|
|
7
6
|
res.status(404);
|
|
@@ -9,6 +8,7 @@ const notFoundHandler = (res) => {
|
|
|
9
8
|
};
|
|
10
9
|
const ercMetadataHandler = async (req, res) => {
|
|
11
10
|
try {
|
|
11
|
+
const { ROOT_URL = 'http://localhost:4010' } = process.env;
|
|
12
12
|
const { services, modules, locale, loaders } = req.unchainedContext;
|
|
13
13
|
const url = new URL(req.url, ROOT_URL);
|
|
14
14
|
if (!url.pathname.toLowerCase().endsWith('.json'))
|
|
@@ -14,7 +14,6 @@ export default function addAssortmentFilter(context: Context, params: Params<'AD
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function addAssortmentLink(context: Context, params: Params<'ADD_
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function addAssortmentMedia(context: Context, params: Params<'ADD
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function addAssortmentProduct(context: Context, params: Params<'A
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -15,7 +15,6 @@ export default function createAssortment(context: Context, params: Params<'CREAT
|
|
|
15
15
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
16
16
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
17
17
|
isActive: boolean;
|
|
18
|
-
isBase: boolean;
|
|
19
18
|
isRoot: boolean;
|
|
20
19
|
meta?: any;
|
|
21
20
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function getAssortment(context: Context, params: Params<'GET'>):
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function getAssortmentChildren(context: Context, params: Params<'
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -28,7 +27,6 @@ export default function getAssortmentChildren(context: Context, params: Params<'
|
|
|
28
27
|
children: {
|
|
29
28
|
_id: string;
|
|
30
29
|
isActive: boolean;
|
|
31
|
-
isBase: boolean;
|
|
32
30
|
isRoot: boolean;
|
|
33
31
|
meta?: any;
|
|
34
32
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function getAssortmentFilters(context: Context, params: Params<'G
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function getAssortmentLinks(context: Context, params: Params<'GET
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -47,7 +46,6 @@ export default function getAssortmentLinks(context: Context, params: Params<'GET
|
|
|
47
46
|
links?: import("@unchainedshop/core-assortments").AssortmentLink[] | undefined;
|
|
48
47
|
products?: import("@unchainedshop/core-assortments").AssortmentProduct[] | undefined;
|
|
49
48
|
isActive?: boolean | undefined;
|
|
50
|
-
isBase?: boolean | undefined;
|
|
51
49
|
isRoot?: boolean | undefined;
|
|
52
50
|
sequence?: number | undefined;
|
|
53
51
|
slugs?: string[];
|
|
@@ -14,7 +14,6 @@ export default function getAssortmentMedia(context: Context, params: Params<'GET
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function getAssortmentProducts(context: Context, params: Params<'
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -23,7 +23,6 @@ import removeAssortmentLink from './removeAssortmentLink.ts';
|
|
|
23
23
|
import getAssortmentLinks from './getAssortmentLinks.ts';
|
|
24
24
|
import reorderAssortmentLinks from './reorderAssortmentLinks.ts';
|
|
25
25
|
import getAssortmentChildren from './getAssortmentChildren.ts';
|
|
26
|
-
import setAssortmentBase from './setAssortmentBase.ts';
|
|
27
26
|
import searchAssortmentProducts from './searchAssortmentProducts.ts';
|
|
28
27
|
import getAssortmentTexts from './getAssortmentTexts.ts';
|
|
29
28
|
import getAssortmentMediaTexts from './getAssortmentMediaTexts.ts';
|
|
@@ -53,7 +52,6 @@ declare const _default: {
|
|
|
53
52
|
GET_LINKS: typeof getAssortmentLinks;
|
|
54
53
|
REORDER_LINKS: typeof reorderAssortmentLinks;
|
|
55
54
|
GET_CHILDREN: typeof getAssortmentChildren;
|
|
56
|
-
SET_BASE: typeof setAssortmentBase;
|
|
57
55
|
SEARCH_PRODUCTS: typeof searchAssortmentProducts;
|
|
58
56
|
GET_TEXTS: typeof getAssortmentTexts;
|
|
59
57
|
GET_MEDIA_TEXTS: typeof getAssortmentMediaTexts;
|
|
@@ -23,7 +23,6 @@ import removeAssortmentLink from "./removeAssortmentLink.js";
|
|
|
23
23
|
import getAssortmentLinks from "./getAssortmentLinks.js";
|
|
24
24
|
import reorderAssortmentLinks from "./reorderAssortmentLinks.js";
|
|
25
25
|
import getAssortmentChildren from "./getAssortmentChildren.js";
|
|
26
|
-
import setAssortmentBase from "./setAssortmentBase.js";
|
|
27
26
|
import searchAssortmentProducts from "./searchAssortmentProducts.js";
|
|
28
27
|
import getAssortmentTexts from "./getAssortmentTexts.js";
|
|
29
28
|
import getAssortmentMediaTexts from "./getAssortmentMediaTexts.js";
|
|
@@ -53,7 +52,6 @@ export default {
|
|
|
53
52
|
GET_LINKS: getAssortmentLinks,
|
|
54
53
|
REORDER_LINKS: reorderAssortmentLinks,
|
|
55
54
|
GET_CHILDREN: getAssortmentChildren,
|
|
56
|
-
SET_BASE: setAssortmentBase,
|
|
57
55
|
SEARCH_PRODUCTS: searchAssortmentProducts,
|
|
58
56
|
GET_TEXTS: getAssortmentTexts,
|
|
59
57
|
GET_MEDIA_TEXTS: getAssortmentMediaTexts,
|
|
@@ -14,7 +14,6 @@ export default function listAssortments(context: Context, params: Params<'LIST'>
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -23,7 +23,6 @@ export default function reorderAssortmentLinks(context: Context, params: Params<
|
|
|
23
23
|
links?: import("@unchainedshop/core-assortments").AssortmentLink[] | undefined;
|
|
24
24
|
products?: import("@unchainedshop/core-assortments").AssortmentProduct[] | undefined;
|
|
25
25
|
isActive?: boolean | undefined;
|
|
26
|
-
isBase?: boolean | undefined;
|
|
27
26
|
isRoot?: boolean | undefined;
|
|
28
27
|
sequence?: number | undefined;
|
|
29
28
|
slugs?: string[];
|
|
@@ -14,7 +14,6 @@ export default function searchAssortmentProducts(context: Context, params: Param
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function updateAssortment(context: Context, params: Params<'UPDAT
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -14,7 +14,6 @@ export default function updateAssortmentStatus(context: Context, params: Params<
|
|
|
14
14
|
links: import("@unchainedshop/core-assortments").AssortmentLink[];
|
|
15
15
|
products: import("@unchainedshop/core-assortments").AssortmentProduct[];
|
|
16
16
|
isActive: boolean;
|
|
17
|
-
isBase: boolean;
|
|
18
17
|
isRoot: boolean;
|
|
19
18
|
meta?: any;
|
|
20
19
|
sequence: number;
|
|
@@ -165,9 +165,6 @@ export declare const actionValidators: {
|
|
|
165
165
|
assortmentId: z.ZodString;
|
|
166
166
|
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
167
167
|
}, z.core.$strip>;
|
|
168
|
-
readonly SET_BASE: z.ZodObject<{
|
|
169
|
-
assortmentId: z.ZodString;
|
|
170
|
-
}, z.core.$strip>;
|
|
171
168
|
readonly SEARCH_PRODUCTS: z.ZodObject<{
|
|
172
169
|
queryString: z.ZodOptional<z.ZodString>;
|
|
173
170
|
includeInactive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -223,7 +220,6 @@ export declare const AssortmentManagementSchema: {
|
|
|
223
220
|
GET_LINKS: "GET_LINKS";
|
|
224
221
|
REORDER_LINKS: "REORDER_LINKS";
|
|
225
222
|
GET_CHILDREN: "GET_CHILDREN";
|
|
226
|
-
SET_BASE: "SET_BASE";
|
|
227
223
|
SEARCH_PRODUCTS: "SEARCH_PRODUCTS";
|
|
228
224
|
}>;
|
|
229
225
|
assortmentId: z.ZodOptional<z.ZodString>;
|
|
@@ -316,7 +312,6 @@ export declare const AssortmentManagementZodSchema: z.ZodObject<{
|
|
|
316
312
|
GET_LINKS: "GET_LINKS";
|
|
317
313
|
REORDER_LINKS: "REORDER_LINKS";
|
|
318
314
|
GET_CHILDREN: "GET_CHILDREN";
|
|
319
|
-
SET_BASE: "SET_BASE";
|
|
320
315
|
SEARCH_PRODUCTS: "SEARCH_PRODUCTS";
|
|
321
316
|
}>;
|
|
322
317
|
assortmentId: z.ZodOptional<z.ZodString>;
|
|
@@ -154,9 +154,6 @@ export const actionValidators = {
|
|
|
154
154
|
assortmentId: z.string().min(1).describe('Assortment ID (optional for root-level)'),
|
|
155
155
|
includeInactive: z.boolean().optional().describe('Include inactive children'),
|
|
156
156
|
}),
|
|
157
|
-
SET_BASE: z.object({
|
|
158
|
-
assortmentId: z.string().min(1).describe('Assortment ID to set as base'),
|
|
159
|
-
}),
|
|
160
157
|
SEARCH_PRODUCTS: z.object({
|
|
161
158
|
assortmentId: z.string().min(1).describe('Assortment ID'),
|
|
162
159
|
...PaginationSchema,
|
|
@@ -197,7 +194,6 @@ export const AssortmentManagementSchema = {
|
|
|
197
194
|
'GET_LINKS',
|
|
198
195
|
'REORDER_LINKS',
|
|
199
196
|
'GET_CHILDREN',
|
|
200
|
-
'SET_BASE',
|
|
201
197
|
'SEARCH_PRODUCTS',
|
|
202
198
|
'GET_TEXTS',
|
|
203
199
|
'GET_MEDIA_TEXTS',
|
|
@@ -76,7 +76,7 @@ export default function confirmOrder(context: Context, params: Params<'CONFIRM_O
|
|
|
76
76
|
countryCode: string;
|
|
77
77
|
currencyCode: string;
|
|
78
78
|
deliveryId?: string;
|
|
79
|
-
|
|
79
|
+
fulfilled?: Date;
|
|
80
80
|
ordered?: Date;
|
|
81
81
|
orderNumber?: string;
|
|
82
82
|
originEnrollmentId?: string;
|