@manyos/smileconnect-api 1.74.0 → 1.74.2

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.
@@ -7,7 +7,9 @@
7
7
  "Bash(npm view *)",
8
8
  "Bash(npm pack *)",
9
9
  "WebFetch(domain:unpkg.com)",
10
- "WebFetch(domain:www.npmjs.com)"
10
+ "WebFetch(domain:www.npmjs.com)",
11
+ "Bash(node *)",
12
+ "Bash(grep -E '^\\(p-queue|proper-lockfile|async-mutex|p-limit\\)$')"
11
13
  ]
12
14
  }
13
15
  }
@@ -153,7 +153,7 @@ async function renameScript(scriptIdOld, scriptIdNew) {
153
153
  })
154
154
  scriptDefOps[usage.scriptType] = scriptDefNew
155
155
  }
156
- setClient(usage.client, clientConfig)
156
+ await setClient(usage.client, clientConfig)
157
157
  }
158
158
  return true;
159
159
  }
package/docs/releases.md CHANGED
@@ -1,7 +1,13 @@
1
1
  # Release Notes
2
2
 
3
3
  ## API
4
- ### 1.73.1 - 29.04.26
4
+ ### 1.74.2 - 20.05.26
5
+ Improve client config validation to avoid duplicate client configs.
6
+
7
+ ### 1.74.1 - 12.05.26
8
+ Fix issue with dynamic openapi spec for worklogs.
9
+
10
+ ### 1.74.0 - 29.04.26
5
11
  Update node.js to v22 and replace request-native with native fetch in adapter.
6
12
 
7
13
  ### 1.73.0 - 28.04.26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyos/smileconnect-api",
3
- "version": "1.74.0",
3
+ "version": "1.74.2",
4
4
  "description": "A proxy and abstraction layer for BMCs IT Service Management Suite",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -317,7 +317,7 @@ module.exports = (function() {
317
317
 
318
318
  routes.post('/clients', isAuthorizedAdmin,
319
319
  checkSchema(clientConfigSchema),
320
- function (req, res, next) {
320
+ async function (req, res, next) {
321
321
  const origData = JSON.parse(JSON.stringify(req.body));
322
322
  setEventData(
323
323
  req,
@@ -336,7 +336,7 @@ module.exports = (function() {
336
336
  try {
337
337
  const clientConfig = req.body.data;
338
338
  const clientName = clientConfig.name;
339
- config.setClient(clientName, clientConfig)
339
+ await config.setClient(clientName, clientConfig)
340
340
  const savedClientConfig = config.getClient(clientName);
341
341
  req.result = {
342
342
  "data": savedClientConfig
@@ -351,7 +351,7 @@ module.exports = (function() {
351
351
 
352
352
  routes.put('/clients/:id', isAuthorizedAdmin,
353
353
  checkSchema(clientConfigSchema),
354
- function (req, res, next) {
354
+ async function (req, res, next) {
355
355
  const id = req.params.id;
356
356
  const origData = JSON.parse(JSON.stringify(req.body));
357
357
  setEventData(
@@ -370,7 +370,7 @@ module.exports = (function() {
370
370
  try {
371
371
  const clientConfig = req.body.data;
372
372
  const clientName = clientConfig.name;
373
- config.setClient(id, clientConfig);
373
+ await config.setClient(id, clientConfig);
374
374
  const savedClientConfig = config.getClient(clientName);
375
375
  req.result = {
376
376
  "data": savedClientConfig
package/util/config.js CHANGED
@@ -1,11 +1,14 @@
1
1
  const fs = require('fs');
2
2
  const fsPromises = require('fs').promises;
3
+ const crypto = require('crypto');
4
+ const pLimit = require('p-limit');
3
5
  require('dotenv').config();
4
6
 
5
7
  const path = require('path');
6
8
  const log = require('@manyos/logger').setupLog('SMILEconnect', path.basename(__filename));
7
9
 
8
10
  const clientConfigFile = 'conf/clients.json';
11
+ const clientConfigWriteLimit = pLimit(1);
9
12
  const arquery = require('../util/arquery');
10
13
  const mappingUtil = require('../util/mappingUtil');
11
14
  const CacheService = require ('../util/cache.service');
@@ -123,17 +126,31 @@ function setMappings(mapping) {
123
126
  return true
124
127
  }
125
128
 
126
- function setClient(clientName, clientConfig) {
129
+ async function setClient(clientName, clientConfig) {
127
130
  log.debug('start to update config in file', clientConfigFile);
128
131
  log.debug('set client data', clientName, clientConfig);
129
- let config = JSON.parse(fs.readFileSync(clientConfigFile)) || {};
130
- config = config.filter(function( obj ) {
131
- return obj.name !== clientName;
132
+ if (!clientConfig || typeof clientConfig !== 'object' || !clientConfig.name) {
133
+ throw new Error('setClient: clientConfig.name is required');
134
+ }
135
+ return clientConfigWriteLimit(async () => {
136
+ let config;
137
+ try {
138
+ const raw = await fsPromises.readFile(clientConfigFile, 'utf8');
139
+ config = JSON.parse(raw);
140
+ } catch (e) {
141
+ log.warn('setClient: cannot parse existing clients.json, starting empty', e.message);
142
+ config = [];
143
+ }
144
+ if (!Array.isArray(config)) config = [];
145
+ const namesToRemove = new Set([clientName, clientConfig.name].filter(Boolean));
146
+ config = config.filter(obj => obj && !namesToRemove.has(obj.name));
147
+ config.push(clientConfig);
148
+ log.debug('new config size', config.length);
149
+ const tmp = `${clientConfigFile}.tmp.${process.pid}.${crypto.randomBytes(4).toString('hex')}`;
150
+ await fsPromises.writeFile(tmp, JSON.stringify(config, null, 2));
151
+ await fsPromises.rename(tmp, clientConfigFile);
152
+ return true;
132
153
  });
133
- config.push(clientConfig)
134
- log.debug('new config', config);
135
- fs.writeFileSync(clientConfigFile, JSON.stringify(config, null, 2));
136
- return true
137
154
  }
138
155
 
139
156
  function deleteClient(clientName) {
@@ -243,7 +260,7 @@ async function checkCustomFormMapping() {
243
260
  }
244
261
  }
245
262
 
246
- function checkClientConfig(client) {
263
+ async function checkClientConfig(client) {
247
264
  let updateRequired = false;
248
265
  const clientKeys = [
249
266
  'cmdbobject',
@@ -361,25 +378,25 @@ function checkClientConfig(client) {
361
378
  log.warn(`check config for client ${client.name} done. Update required.`);
362
379
  client.config = clientConfig;
363
380
  //save
364
- setClient(client.name, client);
381
+ await setClient(client.name, client);
365
382
  log.warn(`check config for client ${client.name} done. Update done.`);
366
383
  }
367
384
  log.info(`check config for client ${client.name} done. No update required.`);
368
385
  }
369
386
 
370
- function checkClientConfigs() {
387
+ async function checkClientConfigs() {
371
388
  log.info('check client configs');
372
389
  const clientConfigs = getClients();
373
- clientConfigs.forEach(clientConfig => {
374
- checkClientConfig(clientConfig);
375
- });
390
+ for (const clientConfig of clientConfigs) {
391
+ await checkClientConfig(clientConfig);
392
+ }
376
393
  log.info('check client configs done');
377
394
  }
378
395
 
379
396
  async function checkConfig() {
380
397
  log.info('check config');
381
398
  checkMapping();
382
- checkClientConfigs();
399
+ await checkClientConfigs();
383
400
  await checkCustomFormMapping();
384
401
  log.info('check config done');
385
402
  }
@@ -434,7 +451,7 @@ async function getDesignPackage(clientId) {
434
451
  const mapping = getDeprecatedMappingAsCustom(config.requestTypeWorkLog)
435
452
  schemas[config.requestTypeWorkLog] = await getObjectSchema(clientConfig[config.requestTypeWorkLog].fields, mapping, config.forms.workLog)
436
453
  schemas[config.requestTypeWorkLog + '_create_update'] = await getObjectSchema(clientConfig[config.requestTypeWorkLog].fields, mapping, config.forms.workLog, true)
437
- paths = {...paths, ... await getPathDef(config.assocTicketType, config.baseURI + '/{ticketId}/worklogs', config.requestTypeWorkLog, true, true)}
454
+ paths = {...paths, ... await getPathDef(`${config.assocTicketType} Worklog`, config.baseURI + '/{ticketId}/worklogs', config.requestTypeWorkLog, true, true)}
438
455
  paths = {...paths, ... await getAttachmentPathDef(config.assocTicketType, config.baseURI + '/{ticketId}/worklogs')}
439
456
  }
440
457
  if (config.requestTemplate && clientConfig[config.requestTemplate].fields && clientConfig[config.requestTemplate].fields.length > 0) {