@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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(cat:*)",
5
+ "Bash(find:*)",
6
+ "Bash(xargs ls:*)"
7
+ ]
8
+ }
9
+ }
package/README.md CHANGED
@@ -1 +1,202 @@
1
- The documentation can be found here: <https://smileconnect.manyosdocs.de>
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.72.3 - 20.01.26
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.72.3",
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",
@@ -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('/auth/realms/itsmproxy/protocol/openid-connect/token')
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
- return {openapi: "3.0.1", info, servers, paths, components: {schemas}}
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
  {
@@ -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 {