@manyos/smileconnect-api 1.72.3 → 1.73.0
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/.claude/settings.local.json +9 -0
- package/README.md +202 -1
- package/docs/releases.md +7 -1
- package/package.json +3 -1
- package/test/incidentTest.js +13 -0
- package/test/testUtils.js +1 -1
- package/todo.md +137 -0
- package/util/arquery.js +15 -2
- package/util/config.js +90 -1
- package/util/responsehandler.js +5 -0
package/README.md
CHANGED
|
@@ -1 +1,202 @@
|
|
|
1
|
-
|
|
1
|
+
# SMILEconnect API
|
|
2
|
+
|
|
3
|
+
A REST API proxy and abstraction layer for BMC HELIX ITSM (IT Service Management).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
SMILEconnect serves as a middleware/API gateway between external systems and BMC Remedy ITSM. It enables rapid interface creation and management for ITSM departments through a configuration-driven approach with minimal customization to the underlying ITSM system.
|
|
8
|
+
|
|
9
|
+
**Version:** 1.72.3
|
|
10
|
+
|
|
11
|
+
## Key Features
|
|
12
|
+
|
|
13
|
+
- **Ticket Management** - Full CRUD operations for Incidents, Changes, Problems, and Work Orders
|
|
14
|
+
- **Task Management** - Create and manage tasks linked to parent tickets
|
|
15
|
+
- **CMDB Integration** - Query, create, and update Configuration Items/Assets
|
|
16
|
+
- **Organizational Data** - Manage persons, support groups, and organizations
|
|
17
|
+
- **Custom Scripts** - Execute custom business logic via sandboxed JavaScript (vm2)
|
|
18
|
+
- **Multi-Tenant** - Support for multiple clients with isolated configurations
|
|
19
|
+
- **Template Management** - Store and retrieve templates for various ticket types
|
|
20
|
+
- **Work Logs & Attachments** - Attach work logs and files to tickets
|
|
21
|
+
|
|
22
|
+
## Technology Stack
|
|
23
|
+
|
|
24
|
+
| Component | Technology |
|
|
25
|
+
|-----------|------------|
|
|
26
|
+
| Runtime | Node.js 21+ |
|
|
27
|
+
| Framework | Express.js 4.17.1 |
|
|
28
|
+
| Authentication | Passport.js with JWT |
|
|
29
|
+
| Logging | Bunyan + @manyos/logger |
|
|
30
|
+
| HTTP Client | request-promise-native, node-fetch |
|
|
31
|
+
| Caching | node-cache |
|
|
32
|
+
| Script Execution | vm2 (sandboxed JS) |
|
|
33
|
+
| Testing | Mocha, Chai |
|
|
34
|
+
|
|
35
|
+
## Project Structure
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
itsmproxy/
|
|
39
|
+
├── app.js # Main entry point (Express server)
|
|
40
|
+
├── package.json # Dependencies and metadata
|
|
41
|
+
├── Dockerfile # Docker configuration (Node.js 21)
|
|
42
|
+
│
|
|
43
|
+
├── controller/ # Business logic layer (12 controllers)
|
|
44
|
+
│ ├── ticketController.js # Incidents, Changes, Problems, WorkOrders
|
|
45
|
+
│ ├── taskController.js # Task management
|
|
46
|
+
│ ├── cmdbobjectController.js # CMDB/Asset operations
|
|
47
|
+
│ ├── scriptController.js # Custom script execution
|
|
48
|
+
│ └── ...
|
|
49
|
+
│
|
|
50
|
+
├── routes/ # Express route handlers (14 route files)
|
|
51
|
+
│ ├── ticketRoutes.js
|
|
52
|
+
│ ├── cmdbObjectRoutes.js
|
|
53
|
+
│ ├── personRoutes.js
|
|
54
|
+
│ └── ...
|
|
55
|
+
│
|
|
56
|
+
├── util/ # Utilities and services
|
|
57
|
+
│ ├── config.js # Client configuration loading
|
|
58
|
+
│ ├── arquery.js # BMC AR Query abstraction
|
|
59
|
+
│ ├── mappingUtil.js # Field mapping utilities
|
|
60
|
+
│ ├── cache.service.js # In-memory caching
|
|
61
|
+
│ └── ...
|
|
62
|
+
│
|
|
63
|
+
├── conf/ # Configuration files
|
|
64
|
+
│ ├── clients.json # Client configurations (multi-tenant)
|
|
65
|
+
│ ├── mapping.json # Field mappings
|
|
66
|
+
│ ├── customFormMapping.json
|
|
67
|
+
│ ├── adapterConfig.js # Adapter configuration
|
|
68
|
+
│ └── scripts/ # Custom business logic scripts (27+)
|
|
69
|
+
│
|
|
70
|
+
├── test/ # Test suite (Mocha/Chai)
|
|
71
|
+
└── docs/ # Documentation
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API Endpoints
|
|
75
|
+
|
|
76
|
+
| Endpoint | Description |
|
|
77
|
+
|----------|-------------|
|
|
78
|
+
| `GET /v1/health` | Health check |
|
|
79
|
+
| `GET/POST /v1/incidents` | Incident management |
|
|
80
|
+
| `GET/POST /v1/changes` | Change management |
|
|
81
|
+
| `GET/POST /v1/problems` | Problem management |
|
|
82
|
+
| `GET/POST /v1/workorders` | Work order management |
|
|
83
|
+
| `GET/POST /v1/cmdbobjects` | CMDB/Asset management |
|
|
84
|
+
| `GET /v1/persons` | Person queries |
|
|
85
|
+
| `GET /v1/supportgroups` | Support group queries |
|
|
86
|
+
| `GET /v1/organisations` | Organization queries |
|
|
87
|
+
| `POST /v1/scriptEndpoints/:name` | Execute custom script |
|
|
88
|
+
| `GET /v1/openapi/:clientId` | OpenAPI specification |
|
|
89
|
+
|
|
90
|
+
## Authentication
|
|
91
|
+
|
|
92
|
+
- **JWT Bearer Token** via Authorization header
|
|
93
|
+
- **SSO Integration** (Keycloak/OAuth2)
|
|
94
|
+
- **Client-based Authorization** - Each client has isolated configuration
|
|
95
|
+
- **Admin Authorization** via `ADMIN_USERS` environment variable
|
|
96
|
+
- **Master Client** - Can impersonate other clients
|
|
97
|
+
- **Rate Limiting** - 10,000 requests per 15 minutes (configurable)
|
|
98
|
+
|
|
99
|
+
## Data Flow
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Client Request (JWT Token)
|
|
103
|
+
↓
|
|
104
|
+
Express Middleware (Auth, Rate Limit, CORS)
|
|
105
|
+
↓
|
|
106
|
+
Route Handler → Controller
|
|
107
|
+
↓
|
|
108
|
+
Pre-Mapping Scripts (optional)
|
|
109
|
+
↓
|
|
110
|
+
Field Mapping (API → Remedy)
|
|
111
|
+
↓
|
|
112
|
+
Post-Mapping Scripts (optional)
|
|
113
|
+
↓
|
|
114
|
+
AR Query (BMC Remedy REST API)
|
|
115
|
+
↓
|
|
116
|
+
Response Processing & Reverse Mapping
|
|
117
|
+
↓
|
|
118
|
+
JSON Response
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
### Environment Variables
|
|
124
|
+
|
|
125
|
+
**Authentication:**
|
|
126
|
+
- `SSO_PUBLIC_KEY` - JWT Public Key
|
|
127
|
+
- `SSO_ISSUER` - JWT Issuer
|
|
128
|
+
- `SSO_AUDIENCE` - JWT Audience
|
|
129
|
+
- `SSO_CLIENTNAME_ATTRIBUTE` - JWT claim for client ID (default: "azp")
|
|
130
|
+
- `SSO_USERNAME_ATTRIBUTE` - JWT claim for username (default: "preferred_username")
|
|
131
|
+
|
|
132
|
+
**Remedy Connection:**
|
|
133
|
+
- `AR_SERVER` - BMC Remedy AR Server host
|
|
134
|
+
- `AR_PORT` - AR Server port
|
|
135
|
+
- `AR_USER` - AR Server user
|
|
136
|
+
- `AR_PASSWORD` - AR Server password
|
|
137
|
+
- `BASEURL` - Remedy API base URL
|
|
138
|
+
|
|
139
|
+
**Application:**
|
|
140
|
+
- `RATE_LIMIT` - Rate limit per 15 minutes (default: 10000)
|
|
141
|
+
- `MAX_FILESIZE` - Max upload size in MB (default: 5)
|
|
142
|
+
- `MAX_HTTP_SOCKETS` - Maximum HTTP connections (default: 10)
|
|
143
|
+
- `LOGLEVEL` - Log level (debug/info/warn/error)
|
|
144
|
+
- `LOG_REQUEST` - Enable request logging (boolean)
|
|
145
|
+
|
|
146
|
+
**Cache TTL (in seconds):**
|
|
147
|
+
- `CACHETTL_CMDB`, `CACHETTL_TICKETS`, `CACHETTL_TASK`
|
|
148
|
+
- `CACHETTL_CHANGE`, `CACHETTL_PEOPLE`, `CACHETTL_ORGDATA`
|
|
149
|
+
- `CACHETTL_TEMPLATE`, `CACHETTL_CONFIG` (default: 3600)
|
|
150
|
+
|
|
151
|
+
**Authorization:**
|
|
152
|
+
- `ADMIN_USERS` - Comma-separated list of admin usernames
|
|
153
|
+
- `MASTER_CLIENTS` - Comma-separated list of master client IDs
|
|
154
|
+
|
|
155
|
+
## Quick Start
|
|
156
|
+
|
|
157
|
+
### Prerequisites
|
|
158
|
+
- Node.js 21+
|
|
159
|
+
- Access to BMC Remedy ITSM
|
|
160
|
+
- SSO/Keycloak configuration
|
|
161
|
+
|
|
162
|
+
### Installation
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm install
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Running
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Development
|
|
172
|
+
npm start
|
|
173
|
+
|
|
174
|
+
# Docker
|
|
175
|
+
docker build -t smileconnect-api .
|
|
176
|
+
docker run -p 3000:3000 smileconnect-api
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Testing
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm test
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
Full documentation is available at: <https://smileconnect.manyosdocs.de>
|
|
188
|
+
|
|
189
|
+
- [Architecture](docs/architecture.md)
|
|
190
|
+
- [Adapter Documentation](docs/adapter.md)
|
|
191
|
+
- [Script System](docs/scripts.md)
|
|
192
|
+
- [Getting Started](docs/getting-started/)
|
|
193
|
+
- [How-To Guides](docs/howto/)
|
|
194
|
+
- [Configuration](docs/configuration/)
|
|
195
|
+
|
|
196
|
+
## Author
|
|
197
|
+
|
|
198
|
+
Robert Hannemann
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
ISC
|
package/docs/releases.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
3
|
## API
|
|
4
|
-
### 1.
|
|
4
|
+
### 1.73.0 - 28.04.26
|
|
5
|
+
Add detectMime flag to attachments & attachments to dynamic openapi spec.
|
|
6
|
+
|
|
7
|
+
### 1.72.4 - 18.03.26
|
|
5
8
|
Improve connection handling on OAUTH2 Adapater & LDAP Adapter
|
|
6
9
|
|
|
7
10
|
### 1.72.2 - 14.11.25
|
|
@@ -239,6 +242,9 @@ e.g.
|
|
|
239
242
|
*/v1/incidents?impersonateUser=abc123
|
|
240
243
|
|
|
241
244
|
## Event Manager
|
|
245
|
+
### 1.37.1 - 21.01.26
|
|
246
|
+
Add instanceid to eventdata.
|
|
247
|
+
|
|
242
248
|
### 1.37.0 - 15.01.25
|
|
243
249
|
Add instanceid to eventdata.
|
|
244
250
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manyos/smileconnect-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.73.0",
|
|
4
4
|
"description": "A proxy and abstraction layer for BMCs IT Service Management Suite",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"scripts": {
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
"express-request-id": "^1.4.1",
|
|
28
28
|
"express-validator": "^6.10.1",
|
|
29
29
|
"fast-xml-parser": "^3.20.3",
|
|
30
|
+
"file-type": "^16.5.4",
|
|
30
31
|
"https-proxy-agent": "^5.0.1",
|
|
31
32
|
"jsonwebtoken": "^9.0.2",
|
|
33
|
+
"mime-types": "^2.1.35",
|
|
32
34
|
"moment": "^2.29.1",
|
|
33
35
|
"mongoose": "^5.12.5",
|
|
34
36
|
"node-cache": "^4.2.1",
|
package/test/incidentTest.js
CHANGED
|
@@ -362,6 +362,19 @@ describe('Integration Tests - Incidents', function () {
|
|
|
362
362
|
})
|
|
363
363
|
})
|
|
364
364
|
|
|
365
|
+
it('it should detect image/png when detectMime=true', function (done) {
|
|
366
|
+
this.timeout(5000);
|
|
367
|
+
chai.request(server)
|
|
368
|
+
.get(attachmentUrl + '?detectMime=true')
|
|
369
|
+
.set('Authorization', 'Bearer ' + authUser.access_token)
|
|
370
|
+
.end(function (err, res) {
|
|
371
|
+
res.should.have.status(200);
|
|
372
|
+
res.should.have.header('content-type', 'image/png');
|
|
373
|
+
res.should.have.header('content-disposition', 'attachment; filename=logo.png');
|
|
374
|
+
done();
|
|
375
|
+
})
|
|
376
|
+
})
|
|
377
|
+
|
|
365
378
|
|
|
366
379
|
it ('it should get all incident worklogs', function (done) {
|
|
367
380
|
this.timeout(5000);
|
package/test/testUtils.js
CHANGED
|
@@ -4,7 +4,7 @@ const expect = chai.expect;
|
|
|
4
4
|
this.authenticateUser = (user, app) =>
|
|
5
5
|
new Promise((resolve, reject) => {
|
|
6
6
|
chai.request('https://sso.manyos.it')
|
|
7
|
-
.post('/
|
|
7
|
+
.post('/realms/itsmproxy/protocol/openid-connect/token')
|
|
8
8
|
.auth(user.id, user.secret)
|
|
9
9
|
.type('form')
|
|
10
10
|
.send({
|
package/todo.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# SMILEconnect API - TODO
|
|
2
|
+
|
|
3
|
+
## Phase 1: Stabilität (Kritisch)
|
|
4
|
+
|
|
5
|
+
### 1.1 Deprecated Dependencies ersetzen
|
|
6
|
+
- [ ] `request` / `request-promise-native` durch `axios` ersetzen
|
|
7
|
+
- Dateien: `util/arquery.js`, `controller/scriptController.js`
|
|
8
|
+
- [ ] `vm2` durch `isolated-vm` ersetzen (Sicherheitslücken)
|
|
9
|
+
- Dateien: `controller/scriptController.js`
|
|
10
|
+
- [ ] `uuid@3` auf `uuid@9` upgraden
|
|
11
|
+
- [ ] `mongoose@5` auf `mongoose@8` upgraden
|
|
12
|
+
- [ ] `socket.io@2` auf `socket.io@4` upgraden
|
|
13
|
+
|
|
14
|
+
### 1.2 Security Headers
|
|
15
|
+
- [ ] `helmet.js` integrieren für Security Headers
|
|
16
|
+
- Content-Security-Policy
|
|
17
|
+
- X-Content-Type-Options
|
|
18
|
+
- Strict-Transport-Security
|
|
19
|
+
- X-Frame-Options
|
|
20
|
+
|
|
21
|
+
### 1.3 Offene TODOs im Code
|
|
22
|
+
- [ ] `app.js:142-143` - Config error abfangen, AdminScope implementieren
|
|
23
|
+
- [ ] `ticketController.js:130, 327` - Code-Cleanup
|
|
24
|
+
- [ ] `taskController.js:189` - Phase handling für Records ohne Phases
|
|
25
|
+
- [ ] `taskController.js:273` - Attachment handling hinzufügen
|
|
26
|
+
- [ ] `taskController.js:421` - Scripts aktivieren
|
|
27
|
+
- [ ] `ticketCIRelationController.js:181, 221` - Impersonate implementieren
|
|
28
|
+
- [ ] `scriptController.js:76` - Script nur einmal hinzufügen
|
|
29
|
+
- [ ] `scriptController.js:119` - Timeout hinzufügen
|
|
30
|
+
- [ ] `ticketWorkLogController.js:52` - Ticket-Existenz prüfen
|
|
31
|
+
- [ ] `arquery.js:185` - Impersonate hinzufügen
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Phase 2: Performance
|
|
36
|
+
|
|
37
|
+
### 2.1 Distributed Caching
|
|
38
|
+
- [ ] Redis-Integration für verteiltes Caching
|
|
39
|
+
- [ ] Fallback auf in-memory wenn Redis nicht verfügbar
|
|
40
|
+
- [ ] Cache-Invalidierung über Pub/Sub
|
|
41
|
+
|
|
42
|
+
### 2.2 Rate Limiting pro Client
|
|
43
|
+
- [ ] Rate Limit basierend auf JWT clientId statt nur IP
|
|
44
|
+
- [ ] Konfigurierbare Limits pro Client in `clients.json`
|
|
45
|
+
|
|
46
|
+
### 2.3 Connection Pooling
|
|
47
|
+
- [ ] Separate Pools für verschiedene Backend-Services
|
|
48
|
+
- [ ] Keep-Alive Connections optimieren
|
|
49
|
+
- [ ] HTTP/2 Support für Remedy-Backend evaluieren
|
|
50
|
+
|
|
51
|
+
### 2.4 Health Check erweitern
|
|
52
|
+
- [ ] Dependency-Status hinzufügen (Remedy, MongoDB, Redis)
|
|
53
|
+
- [ ] Latenz-Metriken
|
|
54
|
+
- [ ] Version und Uptime
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 3: Developer Experience
|
|
59
|
+
|
|
60
|
+
### 3.1 Strukturierte Error Responses
|
|
61
|
+
- [ ] RFC 7807 Problem Details Format implementieren
|
|
62
|
+
- [ ] Einheitliche Error-Struktur über alle Endpoints
|
|
63
|
+
- [ ] Datei: `util/responsehandler.js`
|
|
64
|
+
|
|
65
|
+
### 3.2 Test Coverage
|
|
66
|
+
- [ ] Jest für Unit Tests einführen
|
|
67
|
+
- [ ] Coverage Report (Ziel: 80%)
|
|
68
|
+
- [ ] Testcontainers für Integration Tests
|
|
69
|
+
|
|
70
|
+
### 3.3 OpenAPI Auto-Generation
|
|
71
|
+
- [ ] swagger-jsdoc integrieren
|
|
72
|
+
- [ ] OpenAPI 3.1 Upgrade
|
|
73
|
+
- [ ] Validierung gegen Schema
|
|
74
|
+
|
|
75
|
+
### 3.4 Prometheus Metrics
|
|
76
|
+
- [ ] `prom-client` integrieren
|
|
77
|
+
- [ ] Request Duration Histogram
|
|
78
|
+
- [ ] Request Counter (by endpoint, status)
|
|
79
|
+
- [ ] Cache Hit/Miss Rate
|
|
80
|
+
- [ ] Endpoint: `GET /metrics`
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Phase 4: Neue Features
|
|
85
|
+
|
|
86
|
+
### 4.1 Webhook/Event System
|
|
87
|
+
- [ ] Webhook-Registrierung pro Client
|
|
88
|
+
- [ ] Events: ticket.created, ticket.updated, ticket.resolved
|
|
89
|
+
- [ ] Retry-Logik mit exponential backoff
|
|
90
|
+
- [ ] Signature Verification
|
|
91
|
+
|
|
92
|
+
### 4.2 Batch-Operationen
|
|
93
|
+
- [ ] `POST /v1/incidents/batch` Endpoint
|
|
94
|
+
- [ ] Bulk-Create und Bulk-Update
|
|
95
|
+
- [ ] Transaktionale Verarbeitung
|
|
96
|
+
|
|
97
|
+
### 4.3 API Key Authentifizierung
|
|
98
|
+
- [ ] API Keys als Alternative zu JWT
|
|
99
|
+
- [ ] Key Rotation
|
|
100
|
+
- [ ] Scope-basierte Berechtigungen
|
|
101
|
+
|
|
102
|
+
### 4.4 Audit Trail
|
|
103
|
+
- [ ] Alle Änderungen in Audit-Log speichern
|
|
104
|
+
- [ ] `GET /v1/{type}/{id}/history` Endpoint
|
|
105
|
+
- [ ] Wer hat wann was geändert
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Phase 5: Strategisch
|
|
110
|
+
|
|
111
|
+
### 5.1 TypeScript Migration
|
|
112
|
+
- [ ] TypeScript-Konfiguration aufsetzen
|
|
113
|
+
- [ ] Schrittweise Migration (util/ zuerst)
|
|
114
|
+
- [ ] Type Definitions für alle Entities
|
|
115
|
+
|
|
116
|
+
### 5.2 GraphQL API (Optional)
|
|
117
|
+
- [ ] Apollo Server evaluieren
|
|
118
|
+
- [ ] Schema Design
|
|
119
|
+
- [ ] Parallel zu REST API
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Notizen
|
|
124
|
+
|
|
125
|
+
**Priorität:** Phase 1 > Phase 2 > Phase 3 > Phase 4 > Phase 5
|
|
126
|
+
|
|
127
|
+
**Kritische Sicherheitsprobleme:**
|
|
128
|
+
- `vm2` hat bekannte Sandbox-Escape-Vulnerabilities
|
|
129
|
+
- `request` ist deprecated und erhält keine Security-Updates mehr
|
|
130
|
+
|
|
131
|
+
**Deprecated Dependencies in package-lock.json:**
|
|
132
|
+
- debug (ReDos vulnerability)
|
|
133
|
+
- glob (versions prior to v9)
|
|
134
|
+
- mkdirp (legacy versions)
|
|
135
|
+
- rimraf (versions prior to v4)
|
|
136
|
+
- superagent (upgrade recommended)
|
|
137
|
+
- uuid (Math.random() issue in v3)
|
package/util/arquery.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require('dotenv').config();
|
|
2
2
|
const request = require('request-promise-native');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const mime = require('mime-types');
|
|
5
|
+
const FileType = require('file-type');
|
|
4
6
|
const {getEventLog} = require("./eventLog");
|
|
5
7
|
let log = require('@manyos/logger').setupLog('SMILEconnect', path.basename(__filename));
|
|
6
8
|
|
|
@@ -315,13 +317,24 @@ function getAttachment(form, entryId, attachmentfield) {
|
|
|
315
317
|
|
|
316
318
|
log.debug('start attachment request on:', uri);
|
|
317
319
|
request(options).auth(process.env.AR_USER, process.env.AR_PASSWORD, true)
|
|
318
|
-
.then(function (response) {
|
|
320
|
+
.then(async function (response) {
|
|
319
321
|
log.debug('RAPI return', response.headers);
|
|
320
322
|
const fileName = response.headers['content-disposition'].split(';')[1].split('=')[1];
|
|
321
323
|
log.debug('filename', fileName);
|
|
324
|
+
const fromExt = mime.lookup(fileName);
|
|
325
|
+
let fromMagic = null;
|
|
326
|
+
if (!fromExt) {
|
|
327
|
+
try {
|
|
328
|
+
fromMagic = (await FileType.fromBuffer(response.body))?.mime;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
log.debug('file-type detection failed, falling back to octet-stream', err.message);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const contentType = fromExt || fromMagic || 'application/octet-stream';
|
|
322
334
|
const file = {
|
|
323
335
|
data : response.body,
|
|
324
|
-
fileName : fileName
|
|
336
|
+
fileName : fileName,
|
|
337
|
+
contentType : contentType
|
|
325
338
|
}
|
|
326
339
|
resolve(file);
|
|
327
340
|
})
|
package/util/config.js
CHANGED
|
@@ -435,6 +435,7 @@ async function getDesignPackage(clientId) {
|
|
|
435
435
|
schemas[config.requestTypeWorkLog] = await getObjectSchema(clientConfig[config.requestTypeWorkLog].fields, mapping, config.forms.workLog)
|
|
436
436
|
schemas[config.requestTypeWorkLog + '_create_update'] = await getObjectSchema(clientConfig[config.requestTypeWorkLog].fields, mapping, config.forms.workLog, true)
|
|
437
437
|
paths = {...paths, ... await getPathDef(config.assocTicketType, config.baseURI + '/{ticketId}/worklogs', config.requestTypeWorkLog, true, true)}
|
|
438
|
+
paths = {...paths, ... await getAttachmentPathDef(config.assocTicketType, config.baseURI + '/{ticketId}/worklogs')}
|
|
438
439
|
}
|
|
439
440
|
if (config.requestTemplate && clientConfig[config.requestTemplate].fields && clientConfig[config.requestTemplate].fields.length > 0) {
|
|
440
441
|
const mapping = getDeprecatedMappingAsCustom(config.requestTemplate)
|
|
@@ -552,7 +553,25 @@ async function getDesignPackage(clientId) {
|
|
|
552
553
|
paths = {...paths, ... await getPathDef('cmdbobject', '/v1/cmdbobjects', cmdbSchemaList)}
|
|
553
554
|
}
|
|
554
555
|
|
|
555
|
-
|
|
556
|
+
const attachmentResponse = {
|
|
557
|
+
description: "Attachment binary content. Content-Type is application/octet-stream by default; with ?detectMime=true it reflects the detected MIME type (extension lookup with magic-byte fallback).",
|
|
558
|
+
headers: {
|
|
559
|
+
"Content-Disposition": {
|
|
560
|
+
description: "attachment; filename=<filename>",
|
|
561
|
+
schema: { type: "string" }
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
content: {
|
|
565
|
+
"application/octet-stream": { schema: { type: "string", format: "binary" } },
|
|
566
|
+
"application/pdf": { schema: { type: "string", format: "binary" } },
|
|
567
|
+
"image/png": { schema: { type: "string", format: "binary" } },
|
|
568
|
+
"image/jpeg": { schema: { type: "string", format: "binary" } },
|
|
569
|
+
"image/gif": { schema: { type: "string", format: "binary" } },
|
|
570
|
+
"text/plain": { schema: { type: "string", format: "binary" } }
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {openapi: "3.0.1", info, servers, paths, components: {schemas, responses: {attachmentResponse}}}
|
|
556
575
|
}
|
|
557
576
|
|
|
558
577
|
async function getPathDef(objectName, baseUri, schema, isSubTicket, suppressUpdate, suppressCreate) {
|
|
@@ -632,6 +651,76 @@ async function getPathDef(objectName, baseUri, schema, isSubTicket, suppressUpda
|
|
|
632
651
|
return pathDef
|
|
633
652
|
}
|
|
634
653
|
|
|
654
|
+
async function getAttachmentPathDef(objectName, workLogBaseUri) {
|
|
655
|
+
const ticketIdParam = {
|
|
656
|
+
name: "ticketId",
|
|
657
|
+
description: "id of the parent ticket",
|
|
658
|
+
schema: { "type": "string" },
|
|
659
|
+
in: "path",
|
|
660
|
+
required: true
|
|
661
|
+
}
|
|
662
|
+
const worklogIdParam = {
|
|
663
|
+
name: "worklogId",
|
|
664
|
+
description: "id of the worklog the attachment belongs to",
|
|
665
|
+
schema: { "type": "string" },
|
|
666
|
+
in: "path",
|
|
667
|
+
required: true
|
|
668
|
+
}
|
|
669
|
+
const detectMimeParam = {
|
|
670
|
+
name: "detectMime",
|
|
671
|
+
description: "If set to 'true', the response Content-Type reflects the detected MIME type of the attachment (filename extension lookup with magic-byte fallback). Without the parameter the response keeps the legacy default 'application/octet-stream' for backwards compatibility.",
|
|
672
|
+
schema: {
|
|
673
|
+
type: "string",
|
|
674
|
+
enum: ["true", "false"]
|
|
675
|
+
},
|
|
676
|
+
in: "query",
|
|
677
|
+
required: false,
|
|
678
|
+
examples: {
|
|
679
|
+
detect: { value: "true" }
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const pathDef = {}
|
|
684
|
+
for (const slot of [1, 2, 3]) {
|
|
685
|
+
const uri = `${workLogBaseUri}/{worklogId}/attachments/${slot}`
|
|
686
|
+
pathDef[uri] = {
|
|
687
|
+
parameters: [ticketIdParam, worklogIdParam],
|
|
688
|
+
get: {
|
|
689
|
+
tags: [objectName, 'Attachments'],
|
|
690
|
+
summary: `Download attachment ${slot} of a ${objectName} worklog`,
|
|
691
|
+
description: "Returns the binary attachment. Default Content-Type is application/octet-stream; pass ?detectMime=true to receive the detected MIME type.",
|
|
692
|
+
parameters: [detectMimeParam],
|
|
693
|
+
responses: {
|
|
694
|
+
"200": { "$ref": "#/components/responses/attachmentResponse" }
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
post: {
|
|
698
|
+
tags: [objectName, 'Attachments'],
|
|
699
|
+
summary: `Upload attachment ${slot} for a ${objectName} worklog`,
|
|
700
|
+
description: 'Send the file in the form field "file".',
|
|
701
|
+
requestBody: {
|
|
702
|
+
required: true,
|
|
703
|
+
content: {
|
|
704
|
+
"multipart/form-data": {
|
|
705
|
+
schema: {
|
|
706
|
+
type: "object",
|
|
707
|
+
properties: {
|
|
708
|
+
file: { type: "string", format: "binary" }
|
|
709
|
+
},
|
|
710
|
+
required: ["file"]
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
responses: {
|
|
716
|
+
"200": { description: "Attachment added" }
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return pathDef
|
|
722
|
+
}
|
|
723
|
+
|
|
635
724
|
function getOpenApiParameters(operation) {
|
|
636
725
|
const parameters = [
|
|
637
726
|
{
|
package/util/responsehandler.js
CHANGED
|
@@ -103,6 +103,11 @@ function eventQueueHandler(req, res, next) {
|
|
|
103
103
|
req.log
|
|
104
104
|
).then( success => {
|
|
105
105
|
if (req.downloadResult) {
|
|
106
|
+
const detect = req.query && req.query.detectMime === 'true';
|
|
107
|
+
const contentType = detect
|
|
108
|
+
? (req.downloadResult.contentType || 'application/octet-stream')
|
|
109
|
+
: 'application/octet-stream';
|
|
110
|
+
res.set('Content-Type', contentType);
|
|
106
111
|
res.set('Content-disposition', 'attachment; filename=' + req.downloadResult.fileName);
|
|
107
112
|
res.status(200).send(req.downloadResult.data);
|
|
108
113
|
} else {
|