@manyos/smileconnect-api 1.72.4 → 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/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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyos/smileconnect-api",
3
- "version": "1.72.4",
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/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 {