@@ -3,6 +3,61 @@ import app from '../index.js';
3
3
import StatusService from '../services/status.service.js' ;
4
4
import logger from '../services/logger.js' ;
5
5
6
+ // Type definitions for the diagnostic response
7
+ interface OctokitTestResult {
8
+ success : boolean ;
9
+ appName ?: string ;
10
+ appOwner ?: string ;
11
+ permissions ?: Record < string , string | undefined > ;
12
+ error ?: string ;
13
+ }
14
+
15
+ interface InstallationDiagnostic {
16
+ index : number ;
17
+ installationId : number ;
18
+ accountLogin : string ;
19
+ accountId : string | number ;
20
+ accountType : string ;
21
+ accountAvatarUrl : string ;
22
+ appId : number ;
23
+ appSlug : string ;
24
+ targetType : string ;
25
+ permissions : Record < string , string | undefined > ;
26
+ events : string [ ] ;
27
+ createdAt : string ;
28
+ updatedAt : string ;
29
+ suspendedAt : string | null ;
30
+ suspendedBy : { login : string ; id : number } | null ;
31
+ hasOctokit : boolean ;
32
+ octokitTest : OctokitTestResult | null ;
33
+ isValid : boolean ;
34
+ validationErrors : string [ ] ;
35
+ }
36
+
37
+ interface AppInfo {
38
+ name : string ;
39
+ description : string ;
40
+ owner : string ;
41
+ htmlUrl : string ;
42
+ permissions : Record < string , string | undefined > ;
43
+ events : string [ ] ;
44
+ }
45
+
46
+ interface DiagnosticsResponse {
47
+ timestamp : string ;
48
+ appConnected : boolean ;
49
+ totalInstallations : number ;
50
+ installations : InstallationDiagnostic [ ] ;
51
+ errors : string [ ] ;
52
+ appInfo : AppInfo | null ;
53
+ summary : {
54
+ validInstallations : number ;
55
+ invalidInstallations : number ;
56
+ organizationNames : string [ ] ;
57
+ accountTypes : Record < string , number > ;
58
+ } ;
59
+ }
60
+
6
61
class SetupController {
7
62
async registrationComplete ( req : Request , res : Response ) {
8
63
try {
@@ -112,6 +167,140 @@ class SetupController {
112
167
}
113
168
}
114
169
170
+ async validateInstallations ( req : Request , res : Response ) {
171
+ try {
172
+ const diagnostics : DiagnosticsResponse = {
173
+ timestamp : new Date ( ) . toISOString ( ) ,
174
+ appConnected : ! ! app . github . app ,
175
+ totalInstallations : app . github . installations . length ,
176
+ installations : [ ] ,
177
+ errors : [ ] ,
178
+ appInfo : null ,
179
+ summary : {
180
+ validInstallations : 0 ,
181
+ invalidInstallations : 0 ,
182
+ organizationNames : [ ] ,
183
+ accountTypes : { }
184
+ }
185
+ } ;
186
+
187
+ // Basic app validation
188
+ if ( ! app . github . app ) {
189
+ diagnostics . errors . push ( 'GitHub App is not initialized' ) ;
190
+ return res . json ( diagnostics ) ;
191
+ }
192
+
193
+ // Validate each installation
194
+ for ( let i = 0 ; i < app . github . installations . length ; i ++ ) {
195
+ const { installation, octokit } = app . github . installations [ i ] ;
196
+
197
+ const installationDiag : InstallationDiagnostic = {
198
+ index : i ,
199
+ installationId : installation . id ,
200
+ accountLogin : installation . account ?. login || 'MISSING' ,
201
+ accountId : installation . account ?. id || 'MISSING' ,
202
+ accountType : installation . account ?. type || 'MISSING' ,
203
+ accountAvatarUrl : installation . account ?. avatar_url || 'MISSING' ,
204
+ appId : installation . app_id ,
205
+ appSlug : installation . app_slug ,
206
+ targetType : installation . target_type ,
207
+ permissions : installation . permissions || { } ,
208
+ events : installation . events || [ ] ,
209
+ createdAt : installation . created_at ,
210
+ updatedAt : installation . updated_at ,
211
+ suspendedAt : installation . suspended_at ,
212
+ suspendedBy : installation . suspended_by ,
213
+ hasOctokit : ! ! octokit ,
214
+ octokitTest : null ,
215
+ isValid : true ,
216
+ validationErrors : [ ]
217
+ } ;
218
+
219
+ // Validate required fields
220
+ if ( ! installation . account ?. login ) {
221
+ installationDiag . isValid = false ;
222
+ installationDiag . validationErrors . push ( 'Missing account.login (organization name)' ) ;
223
+ }
224
+
225
+ if ( ! installation . account ?. id ) {
226
+ installationDiag . isValid = false ;
227
+ installationDiag . validationErrors . push ( 'Missing account.id' ) ;
228
+ }
229
+
230
+ if ( ! installation . account ?. type ) {
231
+ installationDiag . isValid = false ;
232
+ installationDiag . validationErrors . push ( 'Missing account.type' ) ;
233
+ }
234
+
235
+ // Test Octokit functionality
236
+ if ( octokit ) {
237
+ try {
238
+ // Test basic API call with the installation's octokit
239
+ const authTest = await octokit . rest . apps . getAuthenticated ( ) ;
240
+ installationDiag . octokitTest = {
241
+ success : true ,
242
+ appName : authTest . data ?. name || 'Unknown' ,
243
+ appOwner : ( authTest . data ?. owner && 'login' in authTest . data . owner ) ? authTest . data . owner . login : 'Unknown' ,
244
+ permissions : authTest . data ?. permissions || { }
245
+ } ;
246
+ } catch ( error ) {
247
+ installationDiag . octokitTest = {
248
+ success : false ,
249
+ error : error instanceof Error ? error . message : 'Unknown error'
250
+ } ;
251
+ installationDiag . isValid = false ;
252
+ installationDiag . validationErrors . push ( `Octokit API test failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
253
+ }
254
+ } else {
255
+ installationDiag . isValid = false ;
256
+ installationDiag . validationErrors . push ( 'Octokit instance is missing' ) ;
257
+ }
258
+
259
+ // Update summary
260
+ if ( installationDiag . isValid ) {
261
+ diagnostics . summary . validInstallations ++ ;
262
+ if ( installation . account ?. login ) {
263
+ diagnostics . summary . organizationNames . push ( installation . account . login ) ;
264
+ }
265
+ } else {
266
+ diagnostics . summary . invalidInstallations ++ ;
267
+ }
268
+
269
+ // Track account types
270
+ const accountType = installation . account ?. type || 'Unknown' ;
271
+ diagnostics . summary . accountTypes [ accountType ] = ( diagnostics . summary . accountTypes [ accountType ] || 0 ) + 1 ;
272
+
273
+ diagnostics . installations . push ( installationDiag ) ;
274
+ }
275
+
276
+ // Additional app-level diagnostics
277
+ try {
278
+ const appInfo = await app . github . app . octokit . rest . apps . getAuthenticated ( ) ;
279
+ diagnostics . appInfo = {
280
+ name : appInfo . data ?. name || 'Unknown' ,
281
+ description : appInfo . data ?. description || 'No description' ,
282
+ owner : ( appInfo . data ?. owner && 'login' in appInfo . data . owner ) ? appInfo . data . owner . login : 'Unknown' ,
283
+ htmlUrl : appInfo . data ?. html_url || 'Unknown' ,
284
+ permissions : appInfo . data ?. permissions || { } ,
285
+ events : appInfo . data ?. events || [ ]
286
+ } ;
287
+ } catch ( error ) {
288
+ diagnostics . errors . push ( `Failed to get app info: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
289
+ }
290
+
291
+ // Sort organization names for easier reading
292
+ diagnostics . summary . organizationNames . sort ( ) ;
293
+
294
+ res . json ( diagnostics ) ;
295
+ } catch ( error ) {
296
+ logger . error ( 'Installation validation failed' , error ) ;
297
+ res . status ( 500 ) . json ( {
298
+ error : 'Installation validation failed' ,
299
+ details : error instanceof Error ? error . message : 'Unknown error'
300
+ } ) ;
301
+ }
302
+ }
303
+
115
304
116
305
}
117
306
0 commit comments