@aj-archipelago/cortex 1.3.56 → 1.3.57
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/.env.sample +3 -1
- package/config.js +23 -0
- package/helper-apps/mogrt-handler/.env.example +24 -0
- package/helper-apps/mogrt-handler/README.md +166 -0
- package/helper-apps/mogrt-handler/glossaryHandler.js +218 -0
- package/helper-apps/mogrt-handler/index.js +213 -0
- package/helper-apps/mogrt-handler/package-lock.json +7106 -0
- package/helper-apps/mogrt-handler/package.json +34 -0
- package/helper-apps/mogrt-handler/s3Handler.js +444 -0
- package/helper-apps/mogrt-handler/start.js +98 -0
- package/helper-apps/mogrt-handler/swagger.js +42 -0
- package/helper-apps/mogrt-handler/swagger.yaml +436 -0
- package/helper-apps/mogrt-handler/tests/integration/api.test.js +226 -0
- package/helper-apps/mogrt-handler/tests/integration/glossary.test.js +106 -0
- package/helper-apps/mogrt-handler/tests/setup.js +8 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.gif +1 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +1 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +1 -0
- package/helper-apps/mogrt-handler/tests/unit/glossary.unit.test.js +118 -0
- package/helper-apps/mogrt-handler/tests/unit/index.test.js +349 -0
- package/helper-apps/mogrt-handler/tests/unit/s3Handler.test.js +204 -0
- package/helper-apps/mogrt-handler/tests/unit/sample.test.js +28 -0
- package/helper-apps/mogrt-handler/vitest.config.js +15 -0
- package/lib/entityConstants.js +1 -1
- package/lib/requestExecutor.js +1 -1
- package/package.json +1 -1
- package/pathways/list_translation_models.js +67 -0
- package/pathways/system/workspaces/workspace_applet_edit.js +187 -0
- package/pathways/translate_apptek.js +11 -0
- package/pathways/translate_google.js +10 -0
- package/pathways/translate_groq.js +36 -0
- package/server/modelExecutor.js +12 -0
- package/server/plugins/apptekTranslatePlugin.js +189 -0
- package/server/plugins/googleTranslatePlugin.js +121 -0
- package/server/plugins/groqChatPlugin.js +108 -0
- package/tests/apptekTranslatePlugin.test.js +228 -0
- package/tests/integration/apptekTranslatePlugin.integration.test.js +156 -0
- package/tests/translate_apptek.test.js +117 -0
package/.env.sample
CHANGED
package/config.js
CHANGED
|
@@ -389,6 +389,29 @@ var config = convict({
|
|
|
389
389
|
"maxTokenLength": 131072,
|
|
390
390
|
"supportsStreaming": true
|
|
391
391
|
},
|
|
392
|
+
"google-translate": {
|
|
393
|
+
"type": "GOOGLE-TRANSLATE",
|
|
394
|
+
"url": "https://translation.googleapis.com/language/translate/v2",
|
|
395
|
+
"headers": {
|
|
396
|
+
"Content-Type": "application/json"
|
|
397
|
+
},
|
|
398
|
+
"requestsPerSecond": 10
|
|
399
|
+
},
|
|
400
|
+
"groq-chat": {
|
|
401
|
+
"type": "GROQ-CHAT",
|
|
402
|
+
"url": "https://api.groq.com/openai/v1/chat/completions",
|
|
403
|
+
"headers": {
|
|
404
|
+
"Authorization": "Bearer {{GROQ_API_KEY}}",
|
|
405
|
+
"Content-Type": "application/json"
|
|
406
|
+
},
|
|
407
|
+
"params": {
|
|
408
|
+
"model": "meta-llama/llama-4-scout-17b-16e-instruct"
|
|
409
|
+
},
|
|
410
|
+
"requestsPerSecond": 10,
|
|
411
|
+
"maxTokenLength": 65536,
|
|
412
|
+
"maxReturnTokens": 4096,
|
|
413
|
+
"supportsStreaming": true
|
|
414
|
+
},
|
|
392
415
|
"claude-35-sonnet-vertex": {
|
|
393
416
|
"type": "CLAUDE-3-VERTEX",
|
|
394
417
|
"url": "{{claudeVertexUrl}}",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Server Configuration
|
|
2
|
+
# Port number for the MOGRT handler service
|
|
3
|
+
PORT=7072
|
|
4
|
+
|
|
5
|
+
# AWS Configuration
|
|
6
|
+
# AWS Region for S3 bucket (default: us-east-1)
|
|
7
|
+
AWS_REGION=us-east-1
|
|
8
|
+
|
|
9
|
+
# S3 bucket name where MOGRT files and manifests will be stored
|
|
10
|
+
S3_BUCKET_NAME=your-bucket-name
|
|
11
|
+
|
|
12
|
+
# AWS credentials for S3 access
|
|
13
|
+
AWS_ACCESS_KEY_ID=your-access-key-id
|
|
14
|
+
AWS_SECRET_ACCESS_KEY=your-secret-access-key
|
|
15
|
+
|
|
16
|
+
# Signed URL Configuration
|
|
17
|
+
# Expiration time for signed URLs in seconds (default: 3600 = 1 hour)
|
|
18
|
+
SIGNED_URL_EXPIRY_SECONDS=3600
|
|
19
|
+
|
|
20
|
+
# Optional: Node environment (development/production)
|
|
21
|
+
NODE_ENV=development
|
|
22
|
+
|
|
23
|
+
APPTEK_TOKEN=
|
|
24
|
+
BASE_URL
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# MOGRT Handler Service
|
|
2
|
+
|
|
3
|
+
A service for managing Motion Graphics Templates (MOGRT) files and their preview GIFs with S3 storage integration.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Setup](#setup)
|
|
7
|
+
- [Environment Variables](#environment-variables)
|
|
8
|
+
- [API Documentation](#api-documentation)
|
|
9
|
+
- [Upload MOGRT Files](#upload-mogrt-files)
|
|
10
|
+
- [Get Master Manifest](#get-master-manifest)
|
|
11
|
+
- [Get Individual Manifest](#get-individual-manifest)
|
|
12
|
+
- [File Structure](#file-structure)
|
|
13
|
+
- [Error Handling](#error-handling)
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
1. Install dependencies:
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. Set up environment variables (see [Environment Variables](#environment-variables) section)
|
|
23
|
+
|
|
24
|
+
3. Start the server:
|
|
25
|
+
```bash
|
|
26
|
+
npm start
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The server will start on port 7072 by default.
|
|
30
|
+
|
|
31
|
+
## Environment Variables
|
|
32
|
+
|
|
33
|
+
| Variable | Description | Default | Required |
|
|
34
|
+
|----------|-------------|---------|----------|
|
|
35
|
+
| `PORT` | Server port | 7072 | No |
|
|
36
|
+
| `AWS_REGION` | AWS region for S3 | us-east-1 | No |
|
|
37
|
+
| `S3_BUCKET_NAME` | S3 bucket name for file storage | - | Yes |
|
|
38
|
+
| `AWS_ACCESS_KEY_ID` | AWS access key | - | Yes |
|
|
39
|
+
| `AWS_SECRET_ACCESS_KEY` | AWS secret key | - | Yes |
|
|
40
|
+
| `SIGNED_URL_EXPIRY_SECONDS` | Expiration time for signed URLs | 3600 (1 hour) | No |
|
|
41
|
+
|
|
42
|
+
## API Documentation
|
|
43
|
+
|
|
44
|
+
### Upload MOGRT Files
|
|
45
|
+
|
|
46
|
+
Upload a MOGRT file with its preview GIF.
|
|
47
|
+
|
|
48
|
+
**Endpoint:** `POST /api/MogrtHandler`
|
|
49
|
+
|
|
50
|
+
**Content-Type:** `multipart/form-data`
|
|
51
|
+
|
|
52
|
+
**Required Files:**
|
|
53
|
+
- A `.mogrt` file
|
|
54
|
+
- A `.gif` preview file
|
|
55
|
+
|
|
56
|
+
**Example Request:**
|
|
57
|
+
```bash
|
|
58
|
+
curl -X POST http://localhost:7072/api/MogrtHandler \
|
|
59
|
+
-F "mogrt=@/path/to/template.mogrt" \
|
|
60
|
+
-F "preview=@/path/to/preview.gif"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Success Response:**
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
67
|
+
"mogrtFile": "uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt",
|
|
68
|
+
"previewFile": "uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif",
|
|
69
|
+
"uploadDate": "2025-02-05T14:05:39Z",
|
|
70
|
+
"mogrtUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt?[signed-url-params]",
|
|
71
|
+
"previewUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif?[signed-url-params]"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Error Responses:**
|
|
76
|
+
- `400 Bad Request`: Missing required files or invalid file types
|
|
77
|
+
- `500 Internal Server Error`: Server or S3 error
|
|
78
|
+
|
|
79
|
+
### Get Master Manifest
|
|
80
|
+
|
|
81
|
+
Retrieve a list of all uploaded MOGRT files.
|
|
82
|
+
|
|
83
|
+
**Endpoint:** `GET /api/MogrtHandler`
|
|
84
|
+
|
|
85
|
+
**Example Request:**
|
|
86
|
+
```bash
|
|
87
|
+
curl http://localhost:7072/api/MogrtHandler
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Success Response:**
|
|
91
|
+
```json
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
95
|
+
"mogrtFile": "uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt",
|
|
96
|
+
"previewFile": "uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif",
|
|
97
|
+
"uploadDate": "2025-02-05T14:05:39Z",
|
|
98
|
+
"mogrtUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt?[signed-url-params]",
|
|
99
|
+
"previewUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif?[signed-url-params]"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Error Response:**
|
|
105
|
+
- `500 Internal Server Error`: Server or S3 error
|
|
106
|
+
|
|
107
|
+
### Get Individual Manifest
|
|
108
|
+
|
|
109
|
+
Retrieve information about a specific MOGRT upload.
|
|
110
|
+
|
|
111
|
+
**Endpoint:** `GET /api/MogrtHandler?manifestId=<uuid>`
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `manifestId` (required): UUID of the upload
|
|
115
|
+
|
|
116
|
+
**Example Request:**
|
|
117
|
+
```bash
|
|
118
|
+
curl http://localhost:7072/api/MogrtHandler?manifestId=550e8400-e29b-41d4-a716-446655440000
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Success Response:**
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
125
|
+
"mogrtFile": "uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt",
|
|
126
|
+
"previewFile": "uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif",
|
|
127
|
+
"uploadDate": "2025-02-05T14:05:39Z",
|
|
128
|
+
"mogrtUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/template.mogrt?[signed-url-params]",
|
|
129
|
+
"previewUrl": "https://bucket.s3.amazonaws.com/uploads/550e8400-e29b-41d4-a716-446655440000/preview.gif?[signed-url-params]"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Error Responses:**
|
|
134
|
+
- `404 Not Found`: Manifest not found
|
|
135
|
+
- `500 Internal Server Error`: Server or S3 error
|
|
136
|
+
|
|
137
|
+
## File Structure
|
|
138
|
+
|
|
139
|
+
Files are organized in S3 with the following structure:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
bucket/
|
|
143
|
+
├── master-manifest.json
|
|
144
|
+
└── uploads/
|
|
145
|
+
└── <uuid>/
|
|
146
|
+
├── template.mogrt
|
|
147
|
+
├── preview.gif
|
|
148
|
+
└── manifest.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Error Handling
|
|
152
|
+
|
|
153
|
+
All endpoints return errors in the following format:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"error": "Error message description"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Common error scenarios:
|
|
162
|
+
1. Missing required files in upload
|
|
163
|
+
2. Invalid file types (only .mogrt and .gif allowed)
|
|
164
|
+
3. S3 access or permission issues
|
|
165
|
+
4. Missing or invalid manifest ID
|
|
166
|
+
5. Server configuration errors (missing environment variables)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import { saveGlossaryId, getGlossaryVersions, getGlossaryVersion } from './s3Handler.js';
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
const APPTEK_BASE_URL = process.env.APPTEK_API_URL || 'https://api.apptek.com/api/v2/glossary';
|
|
8
|
+
const APPTEK_TOKEN = process.env.APPTEK_TOKEN;
|
|
9
|
+
|
|
10
|
+
export default async function GlossaryHandler(context, req) {
|
|
11
|
+
const { method, url, body, query, headers } = req;
|
|
12
|
+
// Use token from header if present, else from env
|
|
13
|
+
const token = APPTEK_TOKEN;
|
|
14
|
+
if (!token) {
|
|
15
|
+
context.res = { status: 401, body: { error: 'Missing x-token or APPTEK_TOKEN' } };
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
// List glossaries
|
|
20
|
+
if (method === 'GET' && url.includes('/list')) {
|
|
21
|
+
const resp = await fetch(`${APPTEK_BASE_URL}/list`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: { 'accept': 'application/json', 'x-token': token }
|
|
24
|
+
});
|
|
25
|
+
const data = await resp.json();
|
|
26
|
+
context.res = { status: resp.status, body: data };
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Create glossary
|
|
30
|
+
if (method === 'POST' && url.match(/\/api\/glossary\/[a-z]{2}-[a-z]{2}/)) {
|
|
31
|
+
const langPair = url.match(/\/api\/glossary\/([a-z]{2}-[a-z]{2})/)[1];
|
|
32
|
+
body.name = ""
|
|
33
|
+
|
|
34
|
+
for (const entry of body.entries) {
|
|
35
|
+
entry.target_alternatives = [];
|
|
36
|
+
}
|
|
37
|
+
const resp = await fetch(`${APPTEK_BASE_URL}/${langPair}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'accept': 'application/json', 'x-token': token, 'content-type': 'application/json' },
|
|
40
|
+
body: JSON.stringify(body)
|
|
41
|
+
});
|
|
42
|
+
console.log(resp)
|
|
43
|
+
const data = await resp.json();
|
|
44
|
+
|
|
45
|
+
// If successful, save the glossary ID to S3 with versioning
|
|
46
|
+
if (resp.status === 200 && data.glossary_id) {
|
|
47
|
+
try {
|
|
48
|
+
const versionInfo = await saveGlossaryId(data.glossary_id, langPair, name);
|
|
49
|
+
// Add version info to the response
|
|
50
|
+
data.version = {
|
|
51
|
+
versionId: versionInfo.versionId,
|
|
52
|
+
key: versionInfo.key
|
|
53
|
+
};
|
|
54
|
+
} catch (s3Error) {
|
|
55
|
+
console.error('Error saving glossary ID to S3:', s3Error);
|
|
56
|
+
// Don't fail the request if S3 storage fails
|
|
57
|
+
data.versioningError = 'Failed to save glossary version to S3';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
context.res = { status: resp.status, body: data };
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Delete glossary
|
|
65
|
+
if (method === 'DELETE' && url.match(/\/api\/glossary\/.+/)) {
|
|
66
|
+
const glossaryId = url.split('/').pop();
|
|
67
|
+
console.log(`🗑️ Attempting to delete glossary with ID: ${glossaryId}`);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const resp = await fetch(`${APPTEK_BASE_URL}/${glossaryId}`, {
|
|
71
|
+
method: 'DELETE',
|
|
72
|
+
headers: { 'accept': 'application/json', 'x-token': token }
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`📤 Delete request sent, response status: ${resp.status}`);
|
|
76
|
+
|
|
77
|
+
const data = await resp.json().catch(() => {
|
|
78
|
+
console.log(`⚠️ No JSON in delete response, using empty object`);
|
|
79
|
+
return {};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (resp.status === 200) {
|
|
83
|
+
console.log(`✅ Successfully deleted glossary ${glossaryId}`);
|
|
84
|
+
} else {
|
|
85
|
+
console.error(`❌ Failed to delete glossary ${glossaryId}, status: ${resp.status}`, data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
context.res = { status: resp.status, body: data };
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`❌ Error during glossary deletion: ${error.message}`);
|
|
91
|
+
context.res = { status: 500, body: { error: `Error deleting glossary: ${error.message}` } };
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Edit glossary: delete then create
|
|
96
|
+
if (method === 'POST' && url.includes('/edit/')) {
|
|
97
|
+
const glossaryId = url.split('/edit/').pop();
|
|
98
|
+
// 1. Delete
|
|
99
|
+
console.log(`🗑️ Deleting glossary with ID: ${glossaryId} as part of edit operation`);
|
|
100
|
+
try {
|
|
101
|
+
const deleteResp = await fetch(`${APPTEK_BASE_URL}/${glossaryId}`, {
|
|
102
|
+
method: 'DELETE',
|
|
103
|
+
headers: { 'accept': 'application/json', 'x-token': token }
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`📤 Delete request (for edit) sent, response status: ${deleteResp.status}`);
|
|
107
|
+
|
|
108
|
+
if (deleteResp.status === 200) {
|
|
109
|
+
console.log(`✅ Successfully deleted glossary ${glossaryId} for edit operation`);
|
|
110
|
+
} else {
|
|
111
|
+
console.warn(`⚠️ Non-200 status when deleting glossary for edit: ${deleteResp.status}`);
|
|
112
|
+
}
|
|
113
|
+
} catch (deleteError) {
|
|
114
|
+
console.error(`❌ Error during glossary deletion for edit: ${deleteError.message}`);
|
|
115
|
+
// Continue with create even if delete fails
|
|
116
|
+
}
|
|
117
|
+
// 2. Create (reuse create logic)
|
|
118
|
+
const { source_lang_code, target_lang_code, entries } = body;
|
|
119
|
+
body.name = ""
|
|
120
|
+
const langPair = `${source_lang_code}-${target_lang_code}`;
|
|
121
|
+
const resp = await fetch(`${APPTEK_BASE_URL}/${langPair}`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'accept': 'application/json', 'x-token': token, 'content-type': 'application/json' },
|
|
124
|
+
body: JSON.stringify(body)
|
|
125
|
+
});
|
|
126
|
+
const data = await resp.json();
|
|
127
|
+
|
|
128
|
+
// If successful, save the glossary ID to S3 with versioning
|
|
129
|
+
if (resp.status === 200 && data.glossary_id) {
|
|
130
|
+
try {
|
|
131
|
+
const versionInfo = await saveGlossaryId(data.glossary_id, langPair);
|
|
132
|
+
// Add version info to the response
|
|
133
|
+
data.version = {
|
|
134
|
+
versionId: versionInfo.versionId,
|
|
135
|
+
key: versionInfo.key
|
|
136
|
+
};
|
|
137
|
+
} catch (s3Error) {
|
|
138
|
+
console.error('Error saving glossary ID to S3:', s3Error);
|
|
139
|
+
// Don't fail the request if S3 storage fails
|
|
140
|
+
data.versioningError = 'Failed to save glossary version to S3';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
context.res = { status: resp.status, body: data };
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Get versions of a glossary
|
|
148
|
+
if (method === 'GET' && url.match(/\/api\/glossary\/([a-z]{2}-[a-z]{2})\/versions\/(.*)/)) {
|
|
149
|
+
const matches = url.match(/\/api\/glossary\/([a-z]{2}-[a-z]{2})\/versions\/(.*)/);
|
|
150
|
+
const langPair = matches[1];
|
|
151
|
+
const glossaryId = matches[2];
|
|
152
|
+
const name = query.name || '';
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const versions = await getGlossaryVersions(glossaryId, langPair, name);
|
|
156
|
+
context.res = { status: 200, body: { versions } };
|
|
157
|
+
} catch (error) {
|
|
158
|
+
context.res = { status: 500, body: { error: error.message } };
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Get a specific version of a glossary
|
|
164
|
+
if (method === 'GET' && url.match(/\/api\/glossary\/([a-z]{2}-[a-z]{2})\/version\/(.*)\/(.*)/))
|
|
165
|
+
{
|
|
166
|
+
const matches = url.match(/\/api\/glossary\/([a-z]{2}-[a-z]{2})\/version\/(.*)\/(.*)/);
|
|
167
|
+
const langPair = matches[1];
|
|
168
|
+
const glossaryId = matches[2];
|
|
169
|
+
const versionId = matches[3];
|
|
170
|
+
const name = query.name || '';
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const version = await getGlossaryVersion(glossaryId, langPair, versionId, name);
|
|
174
|
+
context.res = { status: 200, body: version };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
context.res = { status: 500, body: { error: error.message } };
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get glossary by ID
|
|
182
|
+
if (method === 'GET' && url.match(/\/api\/glossary\/([^/]+)$/))
|
|
183
|
+
{
|
|
184
|
+
const glossaryId = url.match(/\/api\/glossary\/([^/]+)$/)[1];
|
|
185
|
+
console.log(`📖 Fetching glossary with ID: ${glossaryId}`);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const resp = await fetch(`${APPTEK_BASE_URL}/${glossaryId}`, {
|
|
189
|
+
method: 'GET',
|
|
190
|
+
headers: {'x-token': token }
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
console.log(`📤 Get glossary request sent, response status: ${resp.status}`);
|
|
194
|
+
|
|
195
|
+
const data = await resp.json().catch(() => {
|
|
196
|
+
console.log(`⚠️ No JSON in response, using empty object`);
|
|
197
|
+
return {};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (resp.status === 200) {
|
|
201
|
+
console.log(`✅ Successfully retrieved glossary ${glossaryId}`);
|
|
202
|
+
} else {
|
|
203
|
+
console.error(`❌ Failed to retrieve glossary ${glossaryId}, status: ${resp.status}`, data);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
context.res = { status: resp.status, body: data };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`❌ Error retrieving glossary: ${error.message}`);
|
|
209
|
+
context.res = { status: 500, body: { error: `Error retrieving glossary: ${error.message}` } };
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
context.res = { status: 404, body: { error: 'Not found' } };
|
|
215
|
+
} catch (error) {
|
|
216
|
+
context.res = { status: 500, body: { error: error.message } };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { uploadToS3, getManifest, saveManifest, removeFromMasterManifest } from './s3Handler.js';
|
|
2
|
+
import Busboy from 'busboy';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const ALLOWED_EXTENSIONS = {
|
|
7
|
+
MOGRT: '.mogrt',
|
|
8
|
+
PREVIEW: ['.gif', '.mp4']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const validateFiles = (files) => {
|
|
12
|
+
const hasMogrt = files.some(f => path.extname(f.filename).toLowerCase() === ALLOWED_EXTENSIONS.MOGRT);
|
|
13
|
+
const hasPreview = files.some(f => ALLOWED_EXTENSIONS.PREVIEW.includes(path.extname(f.filename).toLowerCase()));
|
|
14
|
+
console.log('Validating files:', hasMogrt, hasPreview);
|
|
15
|
+
if (!hasMogrt || !hasPreview) {
|
|
16
|
+
throw new Error('Both MOGRT and preview files (GIF or MP4) are required');
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function MogrtHandler(context, req) {
|
|
21
|
+
const { method } = req;
|
|
22
|
+
const { manifestId } = req.query;
|
|
23
|
+
const id = req.params?.id;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// GET request to fetch manifest
|
|
27
|
+
if (method === 'GET') {
|
|
28
|
+
const manifest = await getManifest(manifestId || 'master');
|
|
29
|
+
context.res = {
|
|
30
|
+
status: 200,
|
|
31
|
+
body: manifest
|
|
32
|
+
};
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// DELETE request to remove MOGRT item
|
|
37
|
+
if (method === 'DELETE' && id) {
|
|
38
|
+
console.log(`Attempting to delete MOGRT with ID: ${id} from manifest: ${manifestId || 'master'}`);
|
|
39
|
+
try {
|
|
40
|
+
// Use the new removeFromMasterManifest function
|
|
41
|
+
const removed = await removeFromMasterManifest(id);
|
|
42
|
+
|
|
43
|
+
// If the item was not found
|
|
44
|
+
if (!removed) {
|
|
45
|
+
console.log(`MOGRT with ID ${id} not found in manifest`);
|
|
46
|
+
context.res = {
|
|
47
|
+
status: 404,
|
|
48
|
+
body: { error: `MOGRT with ID ${id} not found` }
|
|
49
|
+
};
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(`Successfully deleted MOGRT with ID: ${id}`);
|
|
54
|
+
|
|
55
|
+
context.res = {
|
|
56
|
+
status: 200,
|
|
57
|
+
body: {
|
|
58
|
+
success: true,
|
|
59
|
+
message: `MOGRT with ID ${id} successfully deleted`
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`Error deleting MOGRT with ID ${id}:`, error);
|
|
65
|
+
context.res = {
|
|
66
|
+
status: 500,
|
|
67
|
+
body: { error: `Failed to delete MOGRT: ${error.message}` }
|
|
68
|
+
};
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// POST request to upload files
|
|
74
|
+
if (method === 'POST') {
|
|
75
|
+
const files = [];
|
|
76
|
+
const uploadId = uuidv4();
|
|
77
|
+
let name = null;
|
|
78
|
+
console.log('Generated uploadId:', uploadId);
|
|
79
|
+
|
|
80
|
+
const busboy = Busboy({
|
|
81
|
+
headers: {
|
|
82
|
+
'content-type': req.headers['content-type']
|
|
83
|
+
},
|
|
84
|
+
limits: {
|
|
85
|
+
fileSize: 50 * 1024 * 1024, // 50MB limit
|
|
86
|
+
files: 2 // Expect exactly 2 files
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
busboy.on('file', (fieldname, file, fileInfo) => {
|
|
92
|
+
console.log('Processing file:', fieldname, fileInfo.filename);
|
|
93
|
+
const ext = path.extname(fileInfo.filename).toLowerCase();
|
|
94
|
+
|
|
95
|
+
if (ext !== ALLOWED_EXTENSIONS.MOGRT && !ALLOWED_EXTENSIONS.PREVIEW.includes(ext)) {
|
|
96
|
+
const error = new Error('Invalid file type. Only .mogrt, .gif and .mp4 files are allowed.');
|
|
97
|
+
file.resume(); // Drain this file
|
|
98
|
+
reject(error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const chunks = [];
|
|
103
|
+
file.on('data', chunk => {
|
|
104
|
+
console.log('Received chunk of size:', chunk.length);
|
|
105
|
+
chunks.push(chunk);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
file.on('end', () => {
|
|
109
|
+
console.log('File upload complete:', fileInfo.filename);
|
|
110
|
+
const buffer = Buffer.concat(chunks);
|
|
111
|
+
files.push({
|
|
112
|
+
fieldname,
|
|
113
|
+
filename: fileInfo.filename,
|
|
114
|
+
buffer
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
busboy.on('field', (fieldname, value) => {
|
|
120
|
+
console.log('Received field:', fieldname, value);
|
|
121
|
+
if (fieldname === 'name') {
|
|
122
|
+
name = value;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
busboy.on('finish', async () => {
|
|
127
|
+
console.log('All files processed, total files:', files.length);
|
|
128
|
+
try {
|
|
129
|
+
validateFiles(files);
|
|
130
|
+
|
|
131
|
+
const uploadPromises = files.map(file => {
|
|
132
|
+
const key = `${uploadId}/${file.filename}`;
|
|
133
|
+
const ext = path.extname(file.filename).toLowerCase();
|
|
134
|
+
let contentType;
|
|
135
|
+
if (ext === '.mogrt') {
|
|
136
|
+
contentType = 'application/octet-stream';
|
|
137
|
+
} else if (ext === '.gif') {
|
|
138
|
+
contentType = 'image/gif';
|
|
139
|
+
} else if (ext === '.mp4') {
|
|
140
|
+
contentType = 'video/mp4';
|
|
141
|
+
} else {
|
|
142
|
+
contentType = 'application/octet-stream';
|
|
143
|
+
}
|
|
144
|
+
return uploadToS3(key, file.buffer, contentType);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const uploadResults = await Promise.all(uploadPromises);
|
|
148
|
+
console.log('Upload results:', uploadResults);
|
|
149
|
+
|
|
150
|
+
const manifest = {
|
|
151
|
+
id: uploadId,
|
|
152
|
+
name: name || uploadId, // Use uploadId as fallback if name not provided
|
|
153
|
+
mogrtFile: uploadResults.find(r => path.extname(r.key).toLowerCase() === ALLOWED_EXTENSIONS.MOGRT)?.key,
|
|
154
|
+
previewFile: uploadResults.find(r => ALLOWED_EXTENSIONS.PREVIEW.includes(path.extname(r.key).toLowerCase()))?.key,
|
|
155
|
+
uploadDate: new Date().toISOString()
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
await saveManifest(manifest);
|
|
159
|
+
console.log('Manifest saved:', manifest);
|
|
160
|
+
|
|
161
|
+
const masterManifest = await getManifest('master');
|
|
162
|
+
context.res = {
|
|
163
|
+
status: 200,
|
|
164
|
+
body: {
|
|
165
|
+
manifest,
|
|
166
|
+
masterManifest
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
resolve();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('Error processing files:', error);
|
|
172
|
+
context.res = {
|
|
173
|
+
status: 500,
|
|
174
|
+
body: { error: error.message }
|
|
175
|
+
};
|
|
176
|
+
reject(error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
busboy.on('error', (error) => {
|
|
181
|
+
console.error('Busboy error:', error);
|
|
182
|
+
reject(error);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Handle the request data
|
|
186
|
+
try {
|
|
187
|
+
if (typeof req.body === 'string') {
|
|
188
|
+
busboy.end(Buffer.from(req.body));
|
|
189
|
+
} else if (req.body instanceof Buffer) {
|
|
190
|
+
busboy.end(req.body);
|
|
191
|
+
} else if (req.rawBody) {
|
|
192
|
+
busboy.end(req.rawBody);
|
|
193
|
+
} else {
|
|
194
|
+
req.pipe(busboy);
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('Error handling request:', error);
|
|
198
|
+
reject(error);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw new Error('Method not allowed');
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('Handler error:', error);
|
|
206
|
+
context.res = {
|
|
207
|
+
status: error.status || 500,
|
|
208
|
+
body: { error: error.message }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default MogrtHandler;
|