@pagerduty/backstage-plugin-backend 0.13.0 → 0.14.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,459 @@
1
+ 'use strict';
2
+
3
+ var pagerduty = require('../apis/pagerduty.cjs.js');
4
+ var backstagePluginCommon = require('@pagerduty/backstage-plugin-common');
5
+
6
+ class CustomFieldsController {
7
+ logger;
8
+ store;
9
+ constructor(options) {
10
+ this.logger = options.logger;
11
+ this.store = options.store;
12
+ }
13
+ async createCustomField(request, response) {
14
+ let backstageRecordId;
15
+ try {
16
+ const { name, entityPath, description } = request.body;
17
+ const sanitizedName = this.validateFieldInput(name, entityPath);
18
+ const subdomain = this.getSubdomainFromRequest(request);
19
+ const normalizedDescription = this.normalizeDescription(description, entityPath);
20
+ const tempPagerDutyId = `PENDING_${Date.now()}`;
21
+ try {
22
+ const backstageRecord = await this.store.insertCustomField({
23
+ pagerdutyCustomFieldId: tempPagerDutyId,
24
+ pagerdutyCustomFieldDisplayName: name,
25
+ pagerdutyCustomFieldEnabled: true,
26
+ backstageEntityMappingPath: entityPath,
27
+ pagerdutySubdomain: subdomain,
28
+ description: normalizedDescription
29
+ });
30
+ backstageRecordId = backstageRecord.id;
31
+ this.logger.info(
32
+ `Created temporary Backstage record (id=${backstageRecordId}) for custom field: ${name}`
33
+ );
34
+ } catch (error) {
35
+ this.handleDbError(error);
36
+ }
37
+ const pagerDutyRequest = {
38
+ field: {
39
+ data_type: "string",
40
+ description: normalizedDescription,
41
+ display_name: name,
42
+ enabled: true,
43
+ field_type: "single_value",
44
+ name: sanitizedName
45
+ }
46
+ };
47
+ let pagerDutyField;
48
+ try {
49
+ const pagerDutyResponse = await pagerduty.createCustomField({
50
+ request: pagerDutyRequest,
51
+ account: subdomain
52
+ });
53
+ pagerDutyField = pagerDutyResponse.field;
54
+ this.logger.info(
55
+ `Created PagerDuty custom field: ${name} (${pagerDutyField.id})`
56
+ );
57
+ } catch (error) {
58
+ if (backstageRecordId) {
59
+ await this.store.deleteCustomField(backstageRecordId);
60
+ this.logger.info(
61
+ `Rolled back Backstage record (id=${backstageRecordId}) due to PagerDuty creation failure`
62
+ );
63
+ }
64
+ if (error instanceof backstagePluginCommon.HttpError) this.handlePagerDutyError(error);
65
+ throw error;
66
+ }
67
+ try {
68
+ await this.store.updateCustomFieldPagerDutyId(backstageRecordId, pagerDutyField.id);
69
+ const customField = await this.store.findCustomFieldById(backstageRecordId);
70
+ this.logger.info(
71
+ `Successfully created custom field: ${name} (${pagerDutyField.id}) mapped to ${entityPath}`
72
+ );
73
+ response.status(201).json({ customField });
74
+ } catch (error) {
75
+ this.logger.error(
76
+ `Failed to update Backstage record with PagerDuty ID. Manual cleanup may be required for PagerDuty field ${pagerDutyField.id}`
77
+ );
78
+ throw error;
79
+ }
80
+ } catch (error) {
81
+ this.handleUnexpectedError(error, "creating the custom field", response);
82
+ }
83
+ }
84
+ async getCustomFields(request, response) {
85
+ try {
86
+ const subdomain = this.getSubdomainFromRequest(request);
87
+ const enabled = parseEnabledQuery(request.query.enabled);
88
+ const customFields = await this.store.getAllCustomFields(subdomain, {
89
+ enabled
90
+ });
91
+ const responseData = { customFields };
92
+ response.status(200).json(responseData);
93
+ } catch (error) {
94
+ this.handleUnexpectedError(error, "fetching custom fields", response);
95
+ }
96
+ }
97
+ async syncCustomFieldValues(request, response) {
98
+ try {
99
+ const { serviceId, values } = request.body;
100
+ const subdomain = this.getSubdomainFromRequest(request);
101
+ if (!serviceId) {
102
+ throw new backstagePluginCommon.HttpError("Missing required field: serviceId", 400);
103
+ }
104
+ if (!Array.isArray(values) || values.length === 0) {
105
+ response.status(204).end();
106
+ return;
107
+ }
108
+ try {
109
+ const result = await pagerduty.setServiceCustomFieldValues({
110
+ serviceId,
111
+ request: { custom_fields: values },
112
+ account: subdomain
113
+ });
114
+ this.logger.info(
115
+ `Synced ${values.length} custom field value(s) to PagerDuty service ${serviceId}`
116
+ );
117
+ response.status(200).json(result);
118
+ } catch (error) {
119
+ if (error instanceof backstagePluginCommon.HttpError) this.handlePagerDutyError(error);
120
+ throw error;
121
+ }
122
+ } catch (error) {
123
+ this.handleUnexpectedError(
124
+ error,
125
+ "syncing custom field values",
126
+ response
127
+ );
128
+ }
129
+ }
130
+ async updateCustomField(request, response) {
131
+ try {
132
+ const id = parseInt(request.params.id, 10);
133
+ if (isNaN(id)) throw new backstagePluginCommon.HttpError("Invalid id parameter", 400);
134
+ const existing = await this.store.findCustomFieldById(id);
135
+ if (!existing) throw new backstagePluginCommon.HttpError("Custom field not found", 404);
136
+ const { name, entityPath, description } = request.body;
137
+ this.validateFieldInput(name, entityPath);
138
+ await this.validateUniqueConstraints(
139
+ id,
140
+ name,
141
+ entityPath,
142
+ existing.pagerdutySubdomain
143
+ );
144
+ const normalizedDescription = this.normalizeDescription(description, entityPath);
145
+ const snapshot = {
146
+ pagerdutyCustomFieldDisplayName: existing.pagerdutyCustomFieldDisplayName,
147
+ backstageEntityMappingPath: existing.backstageEntityMappingPath,
148
+ description: existing.description
149
+ };
150
+ try {
151
+ await this.store.updateCustomField(id, {
152
+ pagerdutyCustomFieldDisplayName: name,
153
+ backstageEntityMappingPath: entityPath,
154
+ description: normalizedDescription
155
+ });
156
+ this.logger.info(`Updated Backstage record for custom field id=${id}`);
157
+ } catch (error) {
158
+ this.handleDbError(error);
159
+ }
160
+ const pagerDutyRequest = {
161
+ field: { display_name: name, description: normalizedDescription }
162
+ };
163
+ try {
164
+ await pagerduty.updateCustomField({
165
+ fieldId: existing.pagerdutyCustomFieldId,
166
+ request: pagerDutyRequest,
167
+ account: existing.pagerdutySubdomain
168
+ });
169
+ this.logger.info(`Updated PagerDuty custom field: ${name} (${existing.pagerdutyCustomFieldId})`);
170
+ } catch (error) {
171
+ try {
172
+ await this.store.updateCustomField(id, snapshot);
173
+ this.logger.info(
174
+ `Rolled back Backstage record (id=${id}) to previous values due to PagerDuty update failure`
175
+ );
176
+ } catch (rollbackError) {
177
+ this.logger.error(
178
+ `CRITICAL: Failed to rollback Backstage record (id=${id}) after PagerDuty failure. Manual intervention required.`,
179
+ rollbackError
180
+ );
181
+ }
182
+ if (error instanceof backstagePluginCommon.HttpError) this.handlePagerDutyError(error);
183
+ throw error;
184
+ }
185
+ const customField = await this.store.findCustomFieldById(id);
186
+ this.logger.info(`Successfully updated custom field id=${id} (${name})`);
187
+ response.status(200).json({ customField });
188
+ } catch (error) {
189
+ this.handleUnexpectedError(error, "updating the custom field", response);
190
+ }
191
+ }
192
+ async toggleCustomFieldEnabled(request, response) {
193
+ try {
194
+ const id = parseInt(request.params.id, 10);
195
+ if (isNaN(id)) throw new backstagePluginCommon.HttpError("Invalid id parameter", 400);
196
+ const { enabled } = request.body;
197
+ if (typeof enabled !== "boolean") {
198
+ throw new backstagePluginCommon.HttpError("Invalid enabled value", 400);
199
+ }
200
+ const existing = await this.store.findCustomFieldById(id);
201
+ if (!existing) throw new backstagePluginCommon.HttpError("Custom field not found", 404);
202
+ if (existing.pagerdutyCustomFieldEnabled === enabled) {
203
+ response.status(200).json({ customField: existing });
204
+ return;
205
+ }
206
+ const previousEnabled = existing.pagerdutyCustomFieldEnabled;
207
+ try {
208
+ await this.store.setCustomFieldEnabled(id, enabled);
209
+ this.logger.info(
210
+ `Updated Backstage record for custom field id=${id} (enabled=${enabled})`
211
+ );
212
+ } catch (error) {
213
+ this.handleDbError(error);
214
+ }
215
+ const pagerDutyRequest = {
216
+ field: {
217
+ display_name: existing.pagerdutyCustomFieldDisplayName,
218
+ enabled
219
+ }
220
+ };
221
+ try {
222
+ await pagerduty.updateCustomField({
223
+ fieldId: existing.pagerdutyCustomFieldId,
224
+ request: pagerDutyRequest,
225
+ account: existing.pagerdutySubdomain
226
+ });
227
+ this.logger.info(
228
+ `Updated PagerDuty custom field enabled=${enabled}: ${existing.pagerdutyCustomFieldDisplayName} (${existing.pagerdutyCustomFieldId})`
229
+ );
230
+ } catch (error) {
231
+ try {
232
+ await this.store.setCustomFieldEnabled(id, previousEnabled);
233
+ this.logger.info(
234
+ `Rolled back Backstage record (id=${id}) to enabled=${previousEnabled} due to PagerDuty update failure`
235
+ );
236
+ } catch (rollbackError) {
237
+ this.logger.error(
238
+ `CRITICAL: Failed to rollback Backstage record (id=${id}) after PagerDuty failure. Manual intervention required.`,
239
+ rollbackError
240
+ );
241
+ }
242
+ if (error instanceof backstagePluginCommon.HttpError) this.handlePagerDutyError(error);
243
+ throw error;
244
+ }
245
+ const customField = await this.store.findCustomFieldById(id);
246
+ this.logger.info(
247
+ `Successfully toggled custom field id=${id} to enabled=${enabled}`
248
+ );
249
+ response.status(200).json({ customField });
250
+ } catch (error) {
251
+ this.handleUnexpectedError(
252
+ error,
253
+ "toggling the custom field enabled state",
254
+ response
255
+ );
256
+ }
257
+ }
258
+ async deleteCustomField(request, response) {
259
+ try {
260
+ const id = parseInt(request.params.id, 10);
261
+ if (isNaN(id)) throw new backstagePluginCommon.HttpError("Invalid id parameter", 400);
262
+ const existing = await this.store.findCustomFieldById(id);
263
+ if (!existing) throw new backstagePluginCommon.HttpError("Custom field not found", 404);
264
+ try {
265
+ await pagerduty.deleteCustomField({
266
+ fieldId: existing.pagerdutyCustomFieldId,
267
+ account: existing.pagerdutySubdomain
268
+ });
269
+ this.logger.info(
270
+ `Deleted PagerDuty custom field: ${existing.pagerdutyCustomFieldDisplayName} (${existing.pagerdutyCustomFieldId})`
271
+ );
272
+ } catch (error) {
273
+ if (error instanceof backstagePluginCommon.HttpError && error.status === 404) {
274
+ this.logger.info(
275
+ `PagerDuty custom field ${existing.pagerdutyCustomFieldId} already absent (404); proceeding to delete Backstage record id=${id}`
276
+ );
277
+ } else {
278
+ if (error instanceof backstagePluginCommon.HttpError) this.handlePagerDutyError(error);
279
+ throw error;
280
+ }
281
+ }
282
+ await this.store.deleteCustomField(id);
283
+ this.logger.info(
284
+ `Deleted Backstage custom field record id=${id} (${existing.pagerdutyCustomFieldDisplayName})`
285
+ );
286
+ response.status(204).end();
287
+ } catch (error) {
288
+ this.handleUnexpectedError(error, "deleting the custom field", response);
289
+ }
290
+ }
291
+ async validateUniqueConstraints(currentId, name, entityPath, subdomain) {
292
+ const allFields = await this.store.getAllCustomFields(subdomain);
293
+ const duplicateName = allFields.find(
294
+ (field) => field.id !== currentId && field.pagerdutyCustomFieldDisplayName === name
295
+ );
296
+ if (duplicateName) {
297
+ throw new backstagePluginCommon.HttpError(
298
+ "A custom field with this display name already exists",
299
+ 409
300
+ );
301
+ }
302
+ const duplicateEntityPath = allFields.find(
303
+ (field) => field.id !== currentId && field.backstageEntityMappingPath === entityPath
304
+ );
305
+ if (duplicateEntityPath) {
306
+ throw new backstagePluginCommon.HttpError(
307
+ "A custom field with this entity path already exists",
308
+ 409
309
+ );
310
+ }
311
+ }
312
+ validateFieldInput(name, entityPath) {
313
+ if (!name || !entityPath) {
314
+ throw new backstagePluginCommon.HttpError(
315
+ "Missing required fields: name and entityPath are required",
316
+ 400
317
+ );
318
+ }
319
+ const sanitizedName = this.sanitizeFieldName(name);
320
+ if (!sanitizedName) {
321
+ throw new backstagePluginCommon.HttpError(
322
+ "Field name must contain at least one alphanumeric character",
323
+ 400
324
+ );
325
+ }
326
+ return sanitizedName;
327
+ }
328
+ sanitizeFieldName(name) {
329
+ return name.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
330
+ }
331
+ normalizeDescription(description, entityPath) {
332
+ return (description ?? "").trim() || `Backstage custom field: ${entityPath}`;
333
+ }
334
+ getSubdomainFromRequest(request) {
335
+ return request.query.account || "default";
336
+ }
337
+ handleDbError(error) {
338
+ const msg = error instanceof Error ? error.message : String(error);
339
+ const code = error?.code;
340
+ const isUniqueViolation = code === "23505" || // PostgreSQL
341
+ msg.toLowerCase().includes("unique constraint") || // SQLite / generic
342
+ msg.toLowerCase().includes("unique violation");
343
+ if (isUniqueViolation) {
344
+ if (msg.includes("pagerduty_cf_entitypath_subdomain_unique") || msg.includes("backstageEntityMappingPath")) {
345
+ throw new backstagePluginCommon.HttpError(
346
+ "A custom field with this entity path already exists",
347
+ 409
348
+ );
349
+ }
350
+ if (msg.includes("pagerduty_cf_displayname_subdomain_unique") || msg.includes("pagerdutyCustomFieldDisplayName")) {
351
+ throw new backstagePluginCommon.HttpError(
352
+ "A custom field with this display name already exists",
353
+ 409
354
+ );
355
+ }
356
+ throw new backstagePluginCommon.HttpError("A custom field with these values already exists", 409);
357
+ }
358
+ throw error instanceof Error ? error : new Error(String(error));
359
+ }
360
+ handlePagerDutyError(error) {
361
+ if (error.status === 400) {
362
+ const message = error.message.toLowerCase().includes("product limit reached") ? "PagerDuty custom field limit reached. Maximum number of custom fields (15 or 30) has been exceeded." : error.message;
363
+ throw new backstagePluginCommon.HttpError(message, 400);
364
+ }
365
+ if (error.status === 401) {
366
+ throw new backstagePluginCommon.HttpError(
367
+ "Authentication failed. Please check your PagerDuty API credentials.",
368
+ 401
369
+ );
370
+ }
371
+ if (error.status === 403) {
372
+ throw new backstagePluginCommon.HttpError(
373
+ "Authorization failed. You do not have permission to manage custom fields in PagerDuty.",
374
+ 403
375
+ );
376
+ }
377
+ if (error.status === 404) {
378
+ throw new backstagePluginCommon.HttpError("Custom field or service not found in PagerDuty", 404);
379
+ }
380
+ if (error.status === 409) {
381
+ throw new backstagePluginCommon.HttpError(
382
+ "A custom field with this name already exists in PagerDuty",
383
+ 409
384
+ );
385
+ }
386
+ if (error.status === 429) {
387
+ throw new backstagePluginCommon.HttpError(
388
+ "PagerDuty API rate limit exceeded. Please try again in a few moments.",
389
+ 429
390
+ );
391
+ }
392
+ throw error;
393
+ }
394
+ handleUnexpectedError(error, context, response) {
395
+ this.logger.error(`Failed ${context}: ${error}`);
396
+ if (error instanceof backstagePluginCommon.HttpError) {
397
+ response.status(error.status).json({ errors: [error.message] });
398
+ } else {
399
+ response.status(500).json({
400
+ errors: [`An unexpected error occurred while ${context}`]
401
+ });
402
+ }
403
+ }
404
+ async createSyncLog(request, response) {
405
+ try {
406
+ const log = request.body;
407
+ const subdomain = this.getSubdomainFromRequest(request);
408
+ if (!log.errorCode || !log.customFieldId || !log.customFieldName || !log.entityPath || !log.serviceId || !log.serviceName || !log.errorMessage) {
409
+ throw new backstagePluginCommon.HttpError("Missing required fields in sync log", 400);
410
+ }
411
+ await this.store.insertSyncLog({
412
+ errorCode: log.errorCode,
413
+ customFieldId: log.customFieldId,
414
+ customFieldName: log.customFieldName,
415
+ entityPath: log.entityPath,
416
+ serviceId: log.serviceId,
417
+ serviceName: log.serviceName,
418
+ errorMessage: log.errorMessage,
419
+ subdomain
420
+ });
421
+ response.status(201).end();
422
+ } catch (error) {
423
+ this.handleUnexpectedError(error, "creating sync log", response);
424
+ }
425
+ }
426
+ async getSyncLogs(request, response) {
427
+ try {
428
+ const subdomain = this.getSubdomainFromRequest(request);
429
+ const limit = request.query.limit ? parseInt(request.query.limit, 10) : void 0;
430
+ const offset = request.query.offset ? parseInt(request.query.offset, 10) : void 0;
431
+ const severityParam = request.query.severity || void 0;
432
+ const severity = severityParam === "error" || severityParam === "warning" || severityParam === "info" ? severityParam : void 0;
433
+ const search = request.query.search || void 0;
434
+ const customFieldName = request.query.customFieldName || void 0;
435
+ const entityPath = request.query.entityPath || void 0;
436
+ const serviceName = request.query.serviceName || void 0;
437
+ const result = await this.store.getSyncLogs(subdomain, {
438
+ limit,
439
+ offset,
440
+ severity,
441
+ search,
442
+ customFieldName,
443
+ entityPath,
444
+ serviceName
445
+ });
446
+ response.status(200).json(result);
447
+ } catch (error) {
448
+ this.handleUnexpectedError(error, "fetching sync logs", response);
449
+ }
450
+ }
451
+ }
452
+ function parseEnabledQuery(value) {
453
+ if (value === "true") return true;
454
+ if (value === "false") return false;
455
+ return void 0;
456
+ }
457
+
458
+ exports.CustomFieldsController = CustomFieldsController;
459
+ //# sourceMappingURL=customFieldsController.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customFieldsController.cjs.js","sources":["../../src/service/customFieldsController.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport { Request, Response } from 'express';\nimport { PagerDutyBackendStore } from '../db/PagerDutyBackendDatabase';\nimport {\n createCustomField,\n deleteCustomField,\n setServiceCustomFieldValues,\n updateCustomField,\n} from '../apis/pagerduty';\nimport {\n BackstageCustomFieldCreateRequest,\n BackstageCustomFieldToggleEnabledRequest,\n BackstageCustomFieldUpdateRequest,\n BackstageCustomFieldsResponse,\n CustomFieldSyncLogCreateRequest,\n HttpError,\n PagerDutyCustomFieldCreateRequest,\n PagerDutyCustomFieldUpdateRequest,\n PagerDutyServiceCustomFieldValue,\n} from '@pagerduty/backstage-plugin-common';\n\nexport interface CustomFieldsControllerOptions {\n logger: LoggerService;\n store: PagerDutyBackendStore;\n}\n\nexport class CustomFieldsController {\n private readonly logger: LoggerService;\n private readonly store: PagerDutyBackendStore;\n\n constructor(options: CustomFieldsControllerOptions) {\n this.logger = options.logger;\n this.store = options.store;\n }\n\n async createCustomField(request: Request, response: Response): Promise<void> {\n let backstageRecordId: number | undefined;\n\n try {\n const { name, entityPath, description } =\n request.body as BackstageCustomFieldCreateRequest;\n\n const sanitizedName = this.validateFieldInput(name, entityPath);\n const subdomain = this.getSubdomainFromRequest(request);\n const normalizedDescription = this.normalizeDescription(description, entityPath);\n\n // Step 1: Create in Backstage DB first with temporary PagerDuty ID\n const tempPagerDutyId = `PENDING_${Date.now()}`;\n try {\n const backstageRecord = await this.store.insertCustomField({\n pagerdutyCustomFieldId: tempPagerDutyId,\n pagerdutyCustomFieldDisplayName: name,\n pagerdutyCustomFieldEnabled: true,\n backstageEntityMappingPath: entityPath,\n pagerdutySubdomain: subdomain,\n description: normalizedDescription,\n });\n backstageRecordId = backstageRecord.id;\n\n this.logger.info(\n `Created temporary Backstage record (id=${backstageRecordId}) for custom field: ${name}`,\n );\n } catch (error) {\n this.handleDbError(error);\n }\n\n // Step 2: Create in PagerDuty\n const pagerDutyRequest: PagerDutyCustomFieldCreateRequest = {\n field: {\n data_type: 'string',\n description: normalizedDescription,\n display_name: name,\n enabled: true,\n field_type: 'single_value',\n name: sanitizedName,\n },\n };\n\n let pagerDutyField;\n try {\n const pagerDutyResponse = await createCustomField({\n request: pagerDutyRequest,\n account: subdomain,\n });\n pagerDutyField = pagerDutyResponse.field;\n\n this.logger.info(\n `Created PagerDuty custom field: ${name} (${pagerDutyField.id})`,\n );\n } catch (error) {\n // Rollback: Delete the Backstage record since PagerDuty creation failed\n if (backstageRecordId) {\n await this.store.deleteCustomField(backstageRecordId);\n this.logger.info(\n `Rolled back Backstage record (id=${backstageRecordId}) due to PagerDuty creation failure`,\n );\n }\n if (error instanceof HttpError) this.handlePagerDutyError(error);\n throw error;\n }\n\n // Step 3: Update Backstage record with real PagerDuty ID\n try {\n await this.store.updateCustomFieldPagerDutyId(backstageRecordId!, pagerDutyField.id);\n\n const customField = await this.store.findCustomFieldById(backstageRecordId!);\n\n this.logger.info(\n `Successfully created custom field: ${name} (${pagerDutyField.id}) mapped to ${entityPath}`,\n );\n response.status(201).json({ customField });\n } catch (error) {\n this.logger.error(\n `Failed to update Backstage record with PagerDuty ID. Manual cleanup may be required for PagerDuty field ${pagerDutyField.id}`,\n );\n throw error;\n }\n } catch (error) {\n this.handleUnexpectedError(error, 'creating the custom field', response);\n }\n }\n\n async getCustomFields(request: Request, response: Response): Promise<void> {\n try {\n const subdomain = this.getSubdomainFromRequest(request);\n const enabled = parseEnabledQuery(request.query.enabled);\n const customFields = await this.store.getAllCustomFields(subdomain, {\n enabled,\n });\n const responseData: BackstageCustomFieldsResponse = { customFields };\n response.status(200).json(responseData);\n } catch (error) {\n this.handleUnexpectedError(error, 'fetching custom fields', response);\n }\n }\n\n async syncCustomFieldValues(\n request: Request,\n response: Response,\n ): Promise<void> {\n try {\n const { serviceId, values } = request.body as {\n serviceId?: string;\n values?: PagerDutyServiceCustomFieldValue[];\n };\n const subdomain = this.getSubdomainFromRequest(request);\n\n if (!serviceId) {\n throw new HttpError('Missing required field: serviceId', 400);\n }\n if (!Array.isArray(values) || values.length === 0) {\n response.status(204).end();\n return;\n }\n\n try {\n const result = await setServiceCustomFieldValues({\n serviceId,\n request: { custom_fields: values },\n account: subdomain,\n });\n this.logger.info(\n `Synced ${values.length} custom field value(s) to PagerDuty service ${serviceId}`,\n );\n response.status(200).json(result);\n } catch (error) {\n if (error instanceof HttpError) this.handlePagerDutyError(error);\n throw error;\n }\n } catch (error) {\n this.handleUnexpectedError(\n error,\n 'syncing custom field values',\n response,\n );\n }\n }\n\n async updateCustomField(request: Request, response: Response): Promise<void> {\n try {\n const id = parseInt(request.params.id, 10);\n if (isNaN(id)) throw new HttpError('Invalid id parameter', 400);\n\n const existing = await this.store.findCustomFieldById(id);\n if (!existing) throw new HttpError('Custom field not found', 404);\n\n const { name, entityPath, description } =\n request.body as BackstageCustomFieldUpdateRequest;\n\n this.validateFieldInput(name, entityPath);\n\n // Validate uniqueness constraints BEFORE making any changes\n await this.validateUniqueConstraints(\n id,\n name,\n entityPath,\n existing.pagerdutySubdomain,\n );\n\n const normalizedDescription = this.normalizeDescription(description, entityPath);\n\n // Snapshot current values for rollback\n const snapshot = {\n pagerdutyCustomFieldDisplayName: existing.pagerdutyCustomFieldDisplayName,\n backstageEntityMappingPath: existing.backstageEntityMappingPath,\n description: existing.description,\n };\n\n // Step 1: Update Backstage DB first\n try {\n await this.store.updateCustomField(id, {\n pagerdutyCustomFieldDisplayName: name,\n backstageEntityMappingPath: entityPath,\n description: normalizedDescription,\n });\n\n this.logger.info(`Updated Backstage record for custom field id=${id}`);\n } catch (error) {\n this.handleDbError(error);\n }\n\n // Step 2: Update PagerDuty\n const pagerDutyRequest: PagerDutyCustomFieldUpdateRequest = {\n field: { display_name: name, description: normalizedDescription },\n };\n\n try {\n await updateCustomField({\n fieldId: existing.pagerdutyCustomFieldId,\n request: pagerDutyRequest,\n account: existing.pagerdutySubdomain,\n });\n\n this.logger.info(`Updated PagerDuty custom field: ${name} (${existing.pagerdutyCustomFieldId})`);\n } catch (error) {\n // Rollback: Revert Backstage DB to snapshot\n try {\n await this.store.updateCustomField(id, snapshot);\n this.logger.info(\n `Rolled back Backstage record (id=${id}) to previous values due to PagerDuty update failure`,\n );\n } catch (rollbackError) {\n this.logger.error(\n `CRITICAL: Failed to rollback Backstage record (id=${id}) after PagerDuty failure. Manual intervention required.`,\n rollbackError as Error,\n );\n }\n\n if (error instanceof HttpError) this.handlePagerDutyError(error);\n throw error;\n }\n\n // Step 3: Return updated record\n const customField = await this.store.findCustomFieldById(id);\n this.logger.info(`Successfully updated custom field id=${id} (${name})`);\n response.status(200).json({ customField });\n } catch (error) {\n this.handleUnexpectedError(error, 'updating the custom field', response);\n }\n }\n\n async toggleCustomFieldEnabled(\n request: Request,\n response: Response,\n ): Promise<void> {\n try {\n const id = parseInt(request.params.id, 10);\n if (isNaN(id)) throw new HttpError('Invalid id parameter', 400);\n\n const { enabled } = request.body as BackstageCustomFieldToggleEnabledRequest;\n if (typeof enabled !== 'boolean') {\n throw new HttpError('Invalid enabled value', 400);\n }\n\n const existing = await this.store.findCustomFieldById(id);\n if (!existing) throw new HttpError('Custom field not found', 404);\n\n // No-op if already in the requested state\n if (existing.pagerdutyCustomFieldEnabled === enabled) {\n response.status(200).json({ customField: existing });\n return;\n }\n\n const previousEnabled = existing.pagerdutyCustomFieldEnabled;\n\n // Step 1: Update Backstage DB first\n try {\n await this.store.setCustomFieldEnabled(id, enabled);\n this.logger.info(\n `Updated Backstage record for custom field id=${id} (enabled=${enabled})`,\n );\n } catch (error) {\n this.handleDbError(error);\n }\n\n // Step 2: Update PagerDuty. display_name is required by the update request,\n // so we send the existing name unchanged alongside the enabled flag.\n const pagerDutyRequest: PagerDutyCustomFieldUpdateRequest = {\n field: {\n display_name: existing.pagerdutyCustomFieldDisplayName,\n enabled,\n },\n };\n\n try {\n await updateCustomField({\n fieldId: existing.pagerdutyCustomFieldId,\n request: pagerDutyRequest,\n account: existing.pagerdutySubdomain,\n });\n this.logger.info(\n `Updated PagerDuty custom field enabled=${enabled}: ${existing.pagerdutyCustomFieldDisplayName} (${existing.pagerdutyCustomFieldId})`,\n );\n } catch (error) {\n // Rollback: revert Backstage DB to its previous enabled state\n try {\n await this.store.setCustomFieldEnabled(id, previousEnabled);\n this.logger.info(\n `Rolled back Backstage record (id=${id}) to enabled=${previousEnabled} due to PagerDuty update failure`,\n );\n } catch (rollbackError) {\n this.logger.error(\n `CRITICAL: Failed to rollback Backstage record (id=${id}) after PagerDuty failure. Manual intervention required.`,\n rollbackError as Error,\n );\n }\n\n if (error instanceof HttpError) this.handlePagerDutyError(error);\n throw error;\n }\n\n const customField = await this.store.findCustomFieldById(id);\n this.logger.info(\n `Successfully toggled custom field id=${id} to enabled=${enabled}`,\n );\n response.status(200).json({ customField });\n } catch (error) {\n this.handleUnexpectedError(\n error,\n 'toggling the custom field enabled state',\n response,\n );\n }\n }\n\n async deleteCustomField(request: Request, response: Response): Promise<void> {\n try {\n const id = parseInt(request.params.id, 10);\n if (isNaN(id)) throw new HttpError('Invalid id parameter', 400);\n\n const existing = await this.store.findCustomFieldById(id);\n if (!existing) throw new HttpError('Custom field not found', 404);\n\n // Step 1: Delete from PagerDuty first\n try {\n await deleteCustomField({\n fieldId: existing.pagerdutyCustomFieldId,\n account: existing.pagerdutySubdomain,\n });\n this.logger.info(\n `Deleted PagerDuty custom field: ${existing.pagerdutyCustomFieldDisplayName} (${existing.pagerdutyCustomFieldId})`,\n );\n } catch (error) {\n // A 404 means the field no longer exists in PagerDuty. Treat the\n // delete as already done and fall through to remove the Backstage\n // record so the two sides don't drift out of sync.\n if (error instanceof HttpError && error.status === 404) {\n this.logger.info(\n `PagerDuty custom field ${existing.pagerdutyCustomFieldId} already absent (404); proceeding to delete Backstage record id=${id}`,\n );\n } else {\n if (error instanceof HttpError) this.handlePagerDutyError(error);\n throw error;\n }\n }\n\n // Step 2: Delete from Backstage DB\n await this.store.deleteCustomField(id);\n this.logger.info(\n `Deleted Backstage custom field record id=${id} (${existing.pagerdutyCustomFieldDisplayName})`,\n );\n\n response.status(204).end();\n } catch (error) {\n this.handleUnexpectedError(error, 'deleting the custom field', response);\n }\n }\n\n private async validateUniqueConstraints(\n currentId: number,\n name: string,\n entityPath: string,\n subdomain: string,\n ): Promise<void> {\n const allFields = await this.store.getAllCustomFields(subdomain);\n\n // Check if another field (not the current one) has the same display name\n const duplicateName = allFields.find(\n field =>\n field.id !== currentId &&\n field.pagerdutyCustomFieldDisplayName === name,\n );\n if (duplicateName) {\n throw new HttpError(\n 'A custom field with this display name already exists',\n 409,\n );\n }\n\n // Check if another field (not the current one) has the same entity path\n const duplicateEntityPath = allFields.find(\n field =>\n field.id !== currentId &&\n field.backstageEntityMappingPath === entityPath,\n );\n if (duplicateEntityPath) {\n throw new HttpError(\n 'A custom field with this entity path already exists',\n 409,\n );\n }\n }\n\n private validateFieldInput(\n name: string | undefined,\n entityPath: string | undefined,\n ): string {\n if (!name || !entityPath) {\n throw new HttpError(\n 'Missing required fields: name and entityPath are required',\n 400,\n );\n }\n const sanitizedName = this.sanitizeFieldName(name);\n if (!sanitizedName) {\n throw new HttpError(\n 'Field name must contain at least one alphanumeric character',\n 400,\n );\n }\n return sanitizedName;\n }\n\n private sanitizeFieldName(name: string): string {\n return name\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^a-z0-9_]/g, '');\n }\n\n private normalizeDescription(\n description: string | undefined,\n entityPath: string,\n ): string {\n return (description ?? '').trim() || `Backstage custom field: ${entityPath}`;\n }\n\n private getSubdomainFromRequest(request: Request): string {\n return (request.query.account as string) || 'default';\n }\n\n private handleDbError(error: unknown): never {\n const msg = error instanceof Error ? error.message : String(error);\n const code = (error as { code?: string })?.code;\n\n const isUniqueViolation =\n code === '23505' || // PostgreSQL\n msg.toLowerCase().includes('unique constraint') || // SQLite / generic\n msg.toLowerCase().includes('unique violation');\n\n if (isUniqueViolation) {\n if (\n msg.includes('pagerduty_cf_entitypath_subdomain_unique') ||\n msg.includes('backstageEntityMappingPath')\n ) {\n throw new HttpError(\n 'A custom field with this entity path already exists',\n 409,\n );\n }\n if (\n msg.includes('pagerduty_cf_displayname_subdomain_unique') ||\n msg.includes('pagerdutyCustomFieldDisplayName')\n ) {\n throw new HttpError(\n 'A custom field with this display name already exists',\n 409,\n );\n }\n throw new HttpError('A custom field with these values already exists', 409);\n }\n\n throw error instanceof Error ? error : new Error(String(error));\n }\n\n private handlePagerDutyError(error: HttpError): never {\n if (error.status === 400) {\n const message = error.message.toLowerCase().includes('product limit reached')\n ? 'PagerDuty custom field limit reached. Maximum number of custom fields (15 or 30) has been exceeded.'\n : error.message;\n throw new HttpError(message, 400);\n }\n if (error.status === 401) {\n throw new HttpError(\n 'Authentication failed. Please check your PagerDuty API credentials.',\n 401,\n );\n }\n if (error.status === 403) {\n throw new HttpError(\n 'Authorization failed. You do not have permission to manage custom fields in PagerDuty.',\n 403,\n );\n }\n if (error.status === 404) {\n throw new HttpError('Custom field or service not found in PagerDuty', 404);\n }\n if (error.status === 409) {\n throw new HttpError(\n 'A custom field with this name already exists in PagerDuty',\n 409,\n );\n }\n if (error.status === 429) {\n throw new HttpError(\n 'PagerDuty API rate limit exceeded. Please try again in a few moments.',\n 429,\n );\n }\n throw error;\n }\n\n private handleUnexpectedError(\n error: unknown,\n context: string,\n response: Response,\n ): void {\n this.logger.error(`Failed ${context}: ${error}`);\n if (error instanceof HttpError) {\n response.status(error.status).json({ errors: [error.message] });\n } else {\n response.status(500).json({\n errors: [`An unexpected error occurred while ${context}`],\n });\n }\n }\n\n async createSyncLog(request: Request, response: Response): Promise<void> {\n try {\n const log = request.body as CustomFieldSyncLogCreateRequest;\n const subdomain = this.getSubdomainFromRequest(request);\n\n if (\n !log.errorCode ||\n !log.customFieldId ||\n !log.customFieldName ||\n !log.entityPath ||\n !log.serviceId ||\n !log.serviceName ||\n !log.errorMessage\n ) {\n throw new HttpError('Missing required fields in sync log', 400);\n }\n\n await this.store.insertSyncLog({\n errorCode: log.errorCode,\n customFieldId: log.customFieldId,\n customFieldName: log.customFieldName,\n entityPath: log.entityPath,\n serviceId: log.serviceId,\n serviceName: log.serviceName,\n errorMessage: log.errorMessage,\n subdomain,\n });\n\n response.status(201).end();\n } catch (error) {\n this.handleUnexpectedError(error, 'creating sync log', response);\n }\n }\n\n async getSyncLogs(\n request: Request,\n response: Response,\n ): Promise<void> {\n try {\n const subdomain = this.getSubdomainFromRequest(request);\n const limit = request.query.limit\n ? parseInt(request.query.limit as string, 10)\n : undefined;\n const offset = request.query.offset\n ? parseInt(request.query.offset as string, 10)\n : undefined;\n const severityParam = (request.query.severity as string) || undefined;\n const severity =\n severityParam === 'error' ||\n severityParam === 'warning' ||\n severityParam === 'info'\n ? severityParam\n : undefined;\n const search = (request.query.search as string) || undefined;\n const customFieldName =\n (request.query.customFieldName as string) || undefined;\n const entityPath = (request.query.entityPath as string) || undefined;\n const serviceName = (request.query.serviceName as string) || undefined;\n\n const result = await this.store.getSyncLogs(subdomain, {\n limit,\n offset,\n severity,\n search,\n customFieldName,\n entityPath,\n serviceName,\n });\n response.status(200).json(result);\n } catch (error) {\n this.handleUnexpectedError(error, 'fetching sync logs', response);\n }\n }\n}\n\nfunction parseEnabledQuery(value: unknown): boolean | undefined {\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n}\n"],"names":["createCustomField","HttpError","setServiceCustomFieldValues","updateCustomField","deleteCustomField"],"mappings":"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"}