@@ -3,6 +3,61 @@ import app from '../index.js';
33import StatusService from '../services/status.service.js' ;
44import logger from '../services/logger.js' ;
55
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+
661class SetupController {
762 async registrationComplete ( req : Request , res : Response ) {
863 try {
@@ -112,6 +167,140 @@ class SetupController {
112167 }
113168 }
114169
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+
115304
116305}
117306
0 commit comments