@igstack/app-catalog-backend-core 0.3.1-alpha-20260403020019 → 0.3.1-alpha-20260405015231

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.
Files changed (83) hide show
  1. package/dist/db/syncAppCatalog.d.mts +11 -1
  2. package/dist/db/syncAppCatalog.d.mts.map +1 -1
  3. package/dist/db/syncAppCatalog.mjs +54 -2
  4. package/dist/db/syncAppCatalog.mjs.map +1 -1
  5. package/dist/db/tableSyncMagazine.d.mts +18 -0
  6. package/dist/db/tableSyncMagazine.d.mts.map +1 -1
  7. package/dist/db/tableSyncMagazine.mjs +18 -0
  8. package/dist/db/tableSyncMagazine.mjs.map +1 -1
  9. package/dist/generated/prisma/client.mjs.map +1 -1
  10. package/dist/generated/prisma/commonInputTypes.d.mts +50 -50
  11. package/dist/generated/prisma/commonInputTypes.d.mts.map +1 -1
  12. package/dist/generated/prisma/enums.d.mts +2 -0
  13. package/dist/generated/prisma/enums.d.mts.map +1 -1
  14. package/dist/generated/prisma/internal/class.d.mts +48 -0
  15. package/dist/generated/prisma/internal/class.d.mts.map +1 -1
  16. package/dist/generated/prisma/internal/class.mjs +4 -4
  17. package/dist/generated/prisma/internal/class.mjs.map +1 -1
  18. package/dist/generated/prisma/internal/prismaNamespace.d.mts +362 -12
  19. package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
  20. package/dist/generated/prisma/models/DbAppForCatalog.d.mts +198 -3
  21. package/dist/generated/prisma/models/DbAppForCatalog.d.mts.map +1 -1
  22. package/dist/generated/prisma/models/DbAsset.d.mts +2 -8
  23. package/dist/generated/prisma/models/DbAsset.d.mts.map +1 -1
  24. package/dist/generated/prisma/models/DbGroup.d.mts +1124 -0
  25. package/dist/generated/prisma/models/DbGroup.d.mts.map +1 -0
  26. package/dist/generated/prisma/models/DbGroupMembership.d.mts +1199 -0
  27. package/dist/generated/prisma/models/DbGroupMembership.d.mts.map +1 -0
  28. package/dist/generated/prisma/models/DbPerson.d.mts +1155 -0
  29. package/dist/generated/prisma/models/DbPerson.d.mts.map +1 -0
  30. package/dist/generated/prisma/models/DbSubResource.d.mts +1468 -0
  31. package/dist/generated/prisma/models/DbSubResource.d.mts.map +1 -0
  32. package/dist/generated/prisma/models/SourceReference.d.mts +1 -1
  33. package/dist/generated/prisma/models.d.mts +6 -2
  34. package/dist/index.d.mts +5 -3
  35. package/dist/middleware/featureRegistry.mjs +5 -1
  36. package/dist/middleware/featureRegistry.mjs.map +1 -1
  37. package/dist/modules/appCatalog/checkLinks.d.mts.map +1 -1
  38. package/dist/modules/appCatalog/checkLinks.mjs +31 -2
  39. package/dist/modules/appCatalog/checkLinks.mjs.map +1 -1
  40. package/dist/modules/appCatalog/service.mjs +52 -5
  41. package/dist/modules/appCatalog/service.mjs.map +1 -1
  42. package/dist/modules/auth/registerAuthRoutes.mjs +5 -1
  43. package/dist/modules/auth/registerAuthRoutes.mjs.map +1 -1
  44. package/dist/modules/lighthouseKeeper/tools.d.mts +1 -1
  45. package/dist/modules/lighthouseKeeper/tools.mjs +1 -1
  46. package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -1
  47. package/dist/types/common/appCatalogTypes.d.mts +19 -1
  48. package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
  49. package/dist/types/common/approvalMethodTypes.d.mts +52 -36
  50. package/dist/types/common/approvalMethodTypes.d.mts.map +1 -1
  51. package/dist/types/common/personGroupTypes.d.mts +22 -0
  52. package/dist/types/common/personGroupTypes.d.mts.map +1 -0
  53. package/dist/types/common/subResourceTypes.d.mts +24 -0
  54. package/dist/types/common/subResourceTypes.d.mts.map +1 -0
  55. package/package.json +3 -3
  56. package/prisma/schema.prisma +59 -0
  57. package/src/db/syncAppCatalog.ts +73 -1
  58. package/src/db/tableSyncMagazine.ts +18 -0
  59. package/src/generated/prisma/browser.ts +20 -0
  60. package/src/generated/prisma/client.ts +20 -0
  61. package/src/generated/prisma/commonInputTypes.ts +54 -54
  62. package/src/generated/prisma/enums.ts +3 -1
  63. package/src/generated/prisma/internal/class.ts +44 -4
  64. package/src/generated/prisma/internal/prismaNamespace.ts +373 -15
  65. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +58 -0
  66. package/src/generated/prisma/models/DbAppForCatalog.ts +208 -1
  67. package/src/generated/prisma/models/DbAsset.ts +0 -8
  68. package/src/generated/prisma/models/DbGroup.ts +1324 -0
  69. package/src/generated/prisma/models/DbGroupMembership.ts +1432 -0
  70. package/src/generated/prisma/models/DbPerson.ts +1356 -0
  71. package/src/generated/prisma/models/DbSubResource.ts +1692 -0
  72. package/src/generated/prisma/models.ts +4 -0
  73. package/src/middleware/featureRegistry.ts +4 -1
  74. package/src/modules/appCatalog/checkLinks.ts +58 -5
  75. package/src/modules/appCatalog/service.ts +80 -6
  76. package/src/modules/auth/registerAuthRoutes.ts +4 -1
  77. package/src/modules/lighthouseKeeper/tools.ts +1 -1
  78. package/src/prisma-json-types.d.ts +6 -0
  79. package/src/types/common/appCatalogTypes.ts +23 -0
  80. package/src/types/common/approvalMethodTypes.ts +59 -57
  81. package/src/types/common/personGroupTypes.ts +20 -0
  82. package/src/types/common/subResourceTypes.ts +20 -0
  83. package/src/types/index.ts +2 -0
@@ -12,8 +12,12 @@ export type * from './models/user'
12
12
  export type * from './models/session'
13
13
  export type * from './models/account'
14
14
  export type * from './models/verification'
15
+ export type * from './models/DbPerson'
16
+ export type * from './models/DbGroup'
17
+ export type * from './models/DbGroupMembership'
15
18
  export type * from './models/DbApprovalMethod'
16
19
  export type * from './models/DbAppForCatalog'
20
+ export type * from './models/DbSubResource'
17
21
  export type * from './models/DbAppTagDefinition'
18
22
  export type * from './models/DbAsset'
19
23
  export type * from './models/Source'
@@ -74,7 +74,10 @@ const FEATURES: FeatureRegistration[] = [
74
74
  }
75
75
  res.json({ ...session, isAdmin: admin })
76
76
  } else {
77
- res.status(401).json({ error: 'Not authenticated' })
77
+ // Return 200 with null session instead of 401 to avoid
78
+ // browser "Failed to load resource" console errors.
79
+ // The frontend checks for session.user to determine auth state.
80
+ res.json({ session: null, user: null, isAuthenticated: false })
78
81
  }
79
82
  } catch (error) {
80
83
  console.error('[Auth Session Error]', error)
@@ -20,6 +20,59 @@ async function sleep(ms: number): Promise<void> {
20
20
  return new Promise((resolve) => setTimeout(resolve, ms))
21
21
  }
22
22
 
23
+ function classifyNetworkError(err: unknown): string {
24
+ // Node's fetch wraps the real error in error.cause
25
+ const cause =
26
+ err instanceof Error && 'cause' in err && err.cause instanceof Error
27
+ ? err.cause
28
+ : null
29
+
30
+ const hasCode = (obj: unknown): obj is { code: string } =>
31
+ obj != null && typeof obj === 'object' && 'code' in obj
32
+
33
+ const code =
34
+ (hasCode(cause) ? cause.code : null) ?? (hasCode(err) ? err.code : null)
35
+ const causeMsg = cause?.message ?? ''
36
+ const topMsg = err instanceof Error ? err.message : String(err)
37
+
38
+ if (code) {
39
+ switch (code) {
40
+ case 'ENOTFOUND':
41
+ case 'EAI_AGAIN':
42
+ return `DNS resolution failed (${code})`
43
+ case 'ECONNREFUSED':
44
+ return `Connection refused (${code})`
45
+ case 'ETIMEDOUT':
46
+ case 'ECONNRESET':
47
+ return `Connection timeout (${code})`
48
+ case 'ENETUNREACH':
49
+ case 'EHOSTUNREACH':
50
+ return `Network unreachable (${code})`
51
+ case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
52
+ return `SSL certificate error: unable to verify certificate (${code})`
53
+ case 'CERT_HAS_EXPIRED':
54
+ return `SSL certificate error: certificate expired (${code})`
55
+ case 'ERR_TLS_CERT_ALTNAME_INVALID':
56
+ return `SSL certificate error: hostname mismatch (${code})`
57
+ case 'DEPTH_ZERO_SELF_SIGNED_CERT':
58
+ case 'SELF_SIGNED_CERT_IN_CHAIN':
59
+ return `SSL certificate error: self-signed certificate (${code})`
60
+ default:
61
+ if (code.startsWith('ERR_TLS') || code.startsWith('CERT'))
62
+ return `SSL/TLS error (${code}): ${causeMsg || topMsg}`
63
+ return `Network error (${code}): ${causeMsg || topMsg}`
64
+ }
65
+ }
66
+
67
+ const msg = causeMsg || topMsg
68
+ if (/certificate/i.test(msg) || /SSL|TLS/i.test(msg))
69
+ return `SSL/TLS error: ${msg}`
70
+ if (/abort|timeout/i.test(msg)) return `Request timeout: ${msg}`
71
+ if (/dns|resolve/i.test(msg)) return `DNS error: ${msg}`
72
+
73
+ return causeMsg || topMsg
74
+ }
75
+
23
76
  async function checkUrlWithRetry(
24
77
  url: string,
25
78
  options: {
@@ -38,15 +91,15 @@ async function checkUrlWithRetry(
38
91
  })
39
92
  return { status: response.status }
40
93
  } catch (error: unknown) {
41
- const errorMessage =
42
- error instanceof Error ? error.message : String(error)
94
+ const errorMessage = classifyNetworkError(error)
43
95
  lastError = errorMessage
44
96
 
45
- // Check if it's a retryable error (timeout or fetch failed)
97
+ // Check if it's a retryable error (timeout or transient network issues)
46
98
  const isRetryable =
47
- errorMessage.includes('aborted') ||
48
99
  errorMessage.includes('timeout') ||
49
- errorMessage.includes('fetch failed')
100
+ errorMessage.includes('ECONNRESET') ||
101
+ errorMessage.includes('ETIMEDOUT') ||
102
+ errorMessage.includes('EAI_AGAIN')
50
103
 
51
104
  if (isRetryable && attempt < options.maxRetries) {
52
105
  // Exponential backoff: 1s, 2s, 4s
@@ -9,9 +9,10 @@ import type {
9
9
  } from '../../types/common/appCatalogTypes'
10
10
  import type {
11
11
  CustomConfig,
12
- PersonTeamConfig,
13
12
  ServiceConfig,
14
13
  } from '../../types/common/approvalMethodTypes'
14
+ import type { Group, Person } from '../../types/common/personGroupTypes'
15
+ import type { SubResource } from '../../types/common/subResourceTypes'
15
16
  import { omit } from 'radashi'
16
17
  import { parseSourceSlug } from '../../utils/parseSourceSlug'
17
18
 
@@ -96,17 +97,34 @@ export async function getApprovalMethodsFromPrisma(): Promise<
96
97
  case 'service':
97
98
  return {
98
99
  ...baseFields,
99
- type: 'service',
100
+ type: 'service' as const,
100
101
  config: config as ServiceConfig,
101
102
  }
103
+ case 'custom':
104
+ return {
105
+ ...baseFields,
106
+ type: 'custom' as const,
107
+ config: config as CustomConfig,
108
+ }
109
+ case 'noAccessRequired':
110
+ return {
111
+ ...baseFields,
112
+ type: 'noAccessRequired' as const,
113
+ config: config as CustomConfig,
114
+ }
115
+ case 'unknown':
116
+ return {
117
+ ...baseFields,
118
+ type: 'unknown' as const,
119
+ config: config as CustomConfig,
120
+ }
102
121
  case 'personTeam':
122
+ // Legacy: map personTeam to custom
103
123
  return {
104
124
  ...baseFields,
105
- type: 'personTeam',
106
- config: config as PersonTeamConfig,
125
+ type: 'custom' as const,
126
+ config: config as CustomConfig,
107
127
  }
108
- case 'custom':
109
- return { ...baseFields, type: 'custom', config: config as CustomConfig }
110
128
  }
111
129
  })
112
130
  }
@@ -138,6 +156,10 @@ function rowToAppForCatalog(row: AppRowWithSourceRefs): AppForCatalog {
138
156
  const urlIssues = (row.urlIssues as unknown as string[] | null)?.length
139
157
  ? (row.urlIssues as unknown as string[])
140
158
  : undefined
159
+ const tiers =
160
+ row.tiers == null
161
+ ? undefined
162
+ : (row.tiers as unknown as AppForCatalog['tiers'])
141
163
 
142
164
  return {
143
165
  id: row.id,
@@ -157,6 +179,7 @@ function rowToAppForCatalog(row: AppRowWithSourceRefs): AppForCatalog {
157
179
  deprecated,
158
180
  aiPrompt,
159
181
  urlIssues,
182
+ tiers,
160
183
  }
161
184
  }
162
185
 
@@ -265,6 +288,54 @@ export function deriveCategories(apps: AppForCatalog[]): AppCategory[] {
265
288
  return categories
266
289
  }
267
290
 
291
+ export async function getPersonsFromPrisma(): Promise<Person[]> {
292
+ const prisma = getDbClient()
293
+ const rows = await prisma.dbPerson.findMany()
294
+ return rows.map((row) => ({
295
+ slug: row.slug,
296
+ firstName: row.firstName,
297
+ lastName: row.lastName,
298
+ email: row.email ?? undefined,
299
+ }))
300
+ }
301
+
302
+ export async function getGroupsFromPrisma(): Promise<Group[]> {
303
+ const prisma = getDbClient()
304
+ const rows = await prisma.dbGroup.findMany({
305
+ include: { memberships: true },
306
+ })
307
+ return rows.map((row) => ({
308
+ slug: row.slug,
309
+ displayName: row.displayName ?? undefined,
310
+ email: row.email ?? undefined,
311
+ memberSlugs: row.memberships.map((m) => m.personSlug),
312
+ }))
313
+ }
314
+
315
+ export async function getSubResourcesFromPrisma(): Promise<SubResource[]> {
316
+ const prisma = getDbClient()
317
+ const rows = await prisma.dbSubResource.findMany()
318
+ return rows.map((row) => ({
319
+ slug: row.slug,
320
+ displayName: row.displayName,
321
+ description: row.description ?? undefined,
322
+ appSlug: row.appSlug,
323
+ familySlug: row.familySlug ?? undefined,
324
+ tierSlug: row.tierSlug ?? undefined,
325
+ aliases: row.aliases as unknown as string[],
326
+ ownerPersonSlug: row.ownerPersonSlug ?? undefined,
327
+ accessMaintainerGroupSlugs:
328
+ row.accessMaintainerGroupSlugs as unknown as string[],
329
+ accessRequest: row.accessRequest
330
+ ? (row.accessRequest as unknown as SubResource['accessRequest'])
331
+ : undefined,
332
+ accessComments: row.accessComments ?? undefined,
333
+ extra: row.extra
334
+ ? (row.extra as unknown as Record<string, unknown>)
335
+ : undefined,
336
+ }))
337
+ }
338
+
268
339
  export async function getAppCatalogData(
269
340
  getAppsOptional?: () => Promise<AppForCatalog[]>,
270
341
  ): Promise<AppCatalogData> {
@@ -276,5 +347,8 @@ export async function getAppCatalogData(
276
347
  apps,
277
348
  tagsDefinitions: await getGroupingTagDefinitionsFromPrisma(),
278
349
  approvalMethods: await getApprovalMethodsFromPrisma(),
350
+ persons: await getPersonsFromPrisma(),
351
+ groups: await getGroupsFromPrisma(),
352
+ subResources: await getSubResourcesFromPrisma(),
279
353
  }
280
354
  }
@@ -38,7 +38,10 @@ export function registerAuthRoutes(
38
38
  }
39
39
  res.json({ ...session, isAdmin: admin })
40
40
  } else {
41
- res.status(401).json({ error: 'Not authenticated' })
41
+ // Return 200 with null session instead of 401 to avoid
42
+ // browser "Failed to load resource" console errors.
43
+ // The frontend checks for session.user to determine auth state.
44
+ res.json({ session: null, user: null, isAuthenticated: false })
42
45
  }
43
46
  } catch (error) {
44
47
  console.error('[Auth Session Error]', error)
@@ -112,7 +112,7 @@ Return the updated card as a structured JSON object matching the card format:
112
112
  - appUrl: Main application URL
113
113
  - links: Array of {url, title} objects for related links
114
114
  - sources: Array of source URLs (documentation, Confluence pages, etc.)
115
- - accessRequest: Object with approvalMethodId, comments, requestPrompt, postApprovalInstructions, roles, approvers, urls, whoToReachOut
115
+ - accessRequest: Object with approvalMethodSlug, comments, requestPrompt, postApprovalInstructions, roles, approverPersonSlugs, urls
116
116
  - notes: Additional notes
117
117
  - deprecated: Optional deprecation info with type, replacementSlug, comment
118
118
 
@@ -28,6 +28,12 @@ declare global {
28
28
 
29
29
  // AppRole used within accessRequest
30
30
  type AppRole = import('./types/index').AppRole
31
+
32
+ // DbAppForCatalog.tiers - Tier variants (prod/dev)
33
+ type AppTierVariant = import('./types/index').AppTierVariant
34
+
35
+ // DbSubResource.extra - Arbitrary extra data
36
+ type SubResourceExtra = Record<string, unknown>
31
37
  }
32
38
  }
33
39
 
@@ -7,6 +7,24 @@
7
7
  */
8
8
 
9
9
  import type { AppAccessRequest, ApprovalMethod } from './approvalMethodTypes'
10
+ import type { Group, Person } from './personGroupTypes'
11
+ import type { SubResource } from './subResourceTypes'
12
+
13
+ // ============================================================================
14
+ // APP TIER VARIANT
15
+ // ============================================================================
16
+
17
+ /**
18
+ * A tier variant of an app (e.g., prod/dev environments).
19
+ * Each tier can have its own URL and access process.
20
+ */
21
+ export interface AppTierVariant {
22
+ tierSlug: string
23
+ displayName?: string
24
+ description?: string
25
+ appUrl?: string
26
+ accessRequest?: AppAccessRequest
27
+ }
10
28
 
11
29
  // ============================================================================
12
30
  // APP CATALOG TYPES
@@ -53,6 +71,8 @@ export interface AppForCatalog {
53
71
  aiPrompt?: string
54
72
  /** URL health issues detected by automated scanning */
55
73
  urlIssues?: string[]
74
+ /** Optional tier variants (e.g., prod/dev) with per-tier URLs and access */
75
+ tiers?: AppTierVariant[]
56
76
  }
57
77
 
58
78
  // Derived catalog data returned by backend
@@ -98,5 +118,8 @@ export interface AppCatalogData {
98
118
  apps: AppForCatalog[]
99
119
  tagsDefinitions: GroupingTagDefinition[]
100
120
  approvalMethods: AppApprovalMethod[]
121
+ persons: Person[]
122
+ groups: Group[]
123
+ subResources: SubResource[]
101
124
  versions?: AppVersionInfo
102
125
  }
@@ -2,22 +2,19 @@
2
2
  * Approval Method Types
3
3
  *
4
4
  * Global approval method templates that apps can link to.
5
- * Each method has a type (service, personTeam, custom) with type-specific config.
5
+ * Each method has a type (service, custom, noAccessRequired, unknown) with type-specific config.
6
6
  */
7
7
 
8
8
  // ============================================================================
9
9
  // APPROVAL METHOD TYPES (Global Templates)
10
10
  // ============================================================================
11
11
 
12
- export type ApprovalMethodType = 'service' | 'personTeam' | 'custom'
13
-
14
- /**
15
- * Contact for reaching out (not necessarily the approver)
16
- */
17
- export interface ReachOutContact {
18
- displayName: string
19
- contact: string // email, slack handle, etc.
20
- }
12
+ export type ApprovalMethodType =
13
+ | 'service'
14
+ | 'personTeam'
15
+ | 'custom'
16
+ | 'noAccessRequired'
17
+ | 'unknown'
21
18
 
22
19
  /**
23
20
  * Service type config - for bots, ticketing systems, self-service portals
@@ -28,10 +25,11 @@ export interface ServiceConfig {
28
25
  }
29
26
 
30
27
  /**
31
- * Person/Team type config - for human approvers
28
+ * PersonTeam type config - for person/group-based approval
32
29
  */
33
30
  export interface PersonTeamConfig {
34
- reachOutContacts?: ReachOutContact[]
31
+ personSlugs?: string[] // Person references
32
+ groupSlugs?: string[] // Group references
35
33
  }
36
34
 
37
35
  /**
@@ -52,19 +50,15 @@ export type ApprovalMethodConfig =
52
50
  /**
53
51
  * Approval Method - stored in DbApprovalMethod
54
52
  */
55
- // export interface ApprovalMethod {
56
- // slug: string
57
- // type: ApprovalMethodType
58
- // displayName: string
59
- // config?: ApprovalMethodConfig
60
- // createdAt?: Date
61
- // updatedAt?: Date
62
- // }
63
-
64
53
  export type ApprovalMethod = {
65
54
  slug: string
55
+ /**
56
+ * Displayed approval method
57
+ */
66
58
  displayName: string
67
- /** Old/migrated URLs that map to this approval method (e.g., jira.natera.com → natera.atlassian.net) */
59
+ /**
60
+ * Optionally - older name of approval method if there were migration in organization.
61
+ */
68
62
  deprecatedAliases?: string[]
69
63
  createdAt?: Date
70
64
  updatedAt?: Date
@@ -81,6 +75,14 @@ export type ApprovalMethod = {
81
75
  type: 'custom'
82
76
  config: CustomConfig
83
77
  }
78
+ | {
79
+ type: 'noAccessRequired'
80
+ config: CustomConfig
81
+ }
82
+ | {
83
+ type: 'unknown'
84
+ config: CustomConfig
85
+ }
84
86
  )
85
87
 
86
88
  // ============================================================================
@@ -91,19 +93,20 @@ export type ApprovalMethod = {
91
93
  * Role that can be requested for an app
92
94
  */
93
95
  export interface AppRole {
96
+ /**
97
+ * User-friendly role name.
98
+ */
94
99
  displayName: string
100
+ /**
101
+ * Description of role.
102
+ */
95
103
  description?: string
104
+ /**
105
+ * Notes for admins/approvers (Not for requestores)
106
+ */
96
107
  adminNotes?: string
97
108
  }
98
109
 
99
- /**
100
- * Approver contact (person who approves, may differ from reach-out contact)
101
- */
102
- export interface ApproverContact {
103
- displayName: string
104
- contact?: string
105
- }
106
-
107
110
  /**
108
111
  * URL link with optional label
109
112
  */
@@ -113,39 +116,38 @@ export interface ApprovalUrl {
113
116
  }
114
117
 
115
118
  /**
116
- * Per-app approval details - stored as JSON in DbAppForCatalog
117
- * All comment/text-like strings are markdown
119
+ * Used to store ONLY instructions related to access request
118
120
  */
119
121
  export interface AppAccessRequest {
120
- approvalMethodId: string // FK to DbApprovalMethod
121
-
122
- // Common fields (all types) - markdown text
122
+ /**
123
+ * Method of asking for access.
124
+ */
125
+ approvalMethodSlug: string // FK to DbApprovalMethod
126
+
127
+ /**
128
+ * Additional comments, if no other fields are fit.
129
+ */
123
130
  comments?: string
131
+ /**
132
+ * A template to put into a request ask.
133
+ */
124
134
  requestPrompt?: string
135
+ /**
136
+ * Recommended steps post approvel to get access to specific resources.
137
+ */
125
138
  postApprovalInstructions?: string
126
139
 
127
- // Lists
140
+ /**
141
+ * Available roles for given resource.
142
+ */
128
143
  roles?: AppRole[]
129
- approvers?: ApproverContact[]
130
- urls?: ApprovalUrl[]
131
-
132
- // Type-specific (Person/Team only)
133
- whoToReachOut?: string // markdown
134
- }
135
144
 
136
- // ============================================================================
137
- // INPUT TYPES FOR API
138
- // ============================================================================
139
-
140
- export interface CreateApprovalMethodInput {
141
- type: ApprovalMethodType
142
- displayName: string
143
- config?: ApprovalMethodConfig
144
- }
145
-
146
- export interface UpdateApprovalMethodInput {
147
- id: string
148
- type?: ApprovalMethodType
149
- displayName?: string
150
- config?: ApprovalMethodConfig
145
+ /**
146
+ * Individuals that will approve request within given approval method. No need to reach them directly unless specified.
147
+ */
148
+ approverPersonSlugs?: string[] // slugs referencing Person entities
149
+ /**
150
+ * Additional instructions to get approvals
151
+ */
152
+ urls?: ApprovalUrl[]
151
153
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Person & Group Types
3
+ *
4
+ * Persons represent individuals (employees, contacts).
5
+ * Groups represent named sets of persons (teams, distribution lists).
6
+ */
7
+
8
+ export interface Person {
9
+ slug: string
10
+ firstName: string
11
+ lastName: string
12
+ email?: string
13
+ }
14
+
15
+ export interface Group {
16
+ slug: string
17
+ displayName?: string
18
+ email?: string
19
+ memberSlugs: string[]
20
+ }
@@ -0,0 +1,20 @@
1
+ import type { AppAccessRequest } from './approvalMethodTypes'
2
+
3
+ /**
4
+ * A sub-resource within an app.
5
+ * Example: an AWS account under the "AWS Console" app.
6
+ */
7
+ export interface SubResource {
8
+ slug: string
9
+ displayName: string
10
+ description?: string
11
+ appSlug: string
12
+ familySlug?: string
13
+ tierSlug?: string
14
+ aliases: string[]
15
+ ownerPersonSlug?: string
16
+ accessMaintainerGroupSlugs: string[]
17
+ accessRequest?: AppAccessRequest
18
+ accessComments?: string
19
+ extra?: Record<string, unknown>
20
+ }
@@ -3,6 +3,8 @@ export * from './common/sharedTypes.js'
3
3
  export * from './common/resourceTypes.js'
4
4
  export * from './common/appCatalogTypes.js'
5
5
  export * from './common/approvalMethodTypes.js'
6
+ export * from './common/personGroupTypes.js'
7
+ export * from './common/subResourceTypes.js'
6
8
  export * from './common/env/envTypes.js'
7
9
  export * from './common/app/appTypes.js'
8
10
  export * from './common/app/ui/appUiTypes.js'