@@ -4,24 +4,26 @@ import { v4 as uuid } from 'uuid';
44import { ObjectId } from 'mongodb' ;
55import { createHmac } from 'crypto' ;
66import { GitHubService } from './service' ;
7+ import { ProjectDBScheme } from '@hawk.so/types' ;
78import { ContextFactories } from '../../types/graphql' ;
89import { RedisInstallStateStore } from './store/install-state.redis.store' ;
10+ import ProjectModel from '../../models/project' ;
911import WorkspaceModel from '../../models/workspace' ;
1012import { sgr , Effect } from '../../utils/ansi' ;
1113import { databases } from '../../mongo' ;
1214
13- /**
14- * Create GitHub router
15- *
16- * @param factories - context factories for database access
17- * @returns Express router with GitHub integration endpoints
18- */
1915/**
2016 * Default task threshold for automatic task creation
2117 * Minimum totalCount required to trigger auto-task creation
2218 */
2319const DEFAULT_TASK_THRESHOLD_TOTAL_COUNT = 50 ;
2420
21+ /**
22+ * Create GitHub router
23+ *
24+ * @param factories - context factories for database access
25+ * @returns Express router with GitHub integration endpoints
26+ */
2527export function createGitHubRouter ( factories : ContextFactories ) : express . Router {
2628 const router = express . Router ( ) ;
2729 const githubService = new GitHubService ( ) ;
@@ -61,8 +63,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
6163 req : express . Request ,
6264 res : express . Response ,
6365 projectId : string | undefined ,
64- errorMessagePrefix : string = 'perform this action'
65- ) : Promise < { project : any ; workspace : any ; userId : string } | null > {
66+ errorMessagePrefix = 'perform this action'
67+ ) : Promise < { project : ProjectModel ; workspace : WorkspaceModel ; userId : string } | null > {
6668 const userId = req . context ?. user ?. id ;
6769
6870 /**
@@ -143,14 +145,18 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
143145 return null ;
144146 }
145147
146- return { project, workspace, userId } ;
148+ return {
149+ project,
150+ workspace,
151+ userId,
152+ } ;
147153 }
148154
149155 /**
150156 * Log message with GitHub Integration prefix
151157 *
152158 * @param level - log level ('log', 'warn', 'error', 'info')
153- * @param projectId - optional project ID to include in log prefix
159+ * @param projectIdOrFirstArg - optional project ID to include in log prefix, or first log argument if not a valid ObjectId
154160 * @param args - arguments to log
155161 */
156162 function log ( level : 'log' | 'warn' | 'error' | 'info' , projectIdOrFirstArg ?: string | unknown , ...args : unknown [ ] ) : void {
@@ -263,145 +269,20 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
263269 }
264270 } ) ;
265271
266- /**
267- * GET /integration/github/callback?state=<state>&installation_id=<installation_id>
268- * Handle GitHub App installation callback
269- *
270- * @deprecated - now we use /oauth endpoint for both installation and OAuth callbacks
271- */
272- // router.get('/callback', async (req, res, next) => {
273- // try {
274- // const { state, installation_id } = req.query;
275-
276- // /**
277- // * Log callback request for debugging
278- // */
279- // log('info', `Callback received: state=${state}, installation_id=${installation_id}, query=${JSON.stringify(req.query)}`);
280-
281- // /**
282- // * Validate required parameters
283- // */
284- // if (!state || typeof state !== 'string') {
285- // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', {
286- // error: 'Missing or invalid state',
287- // }));
288- // }
289-
290- // if (!installation_id || typeof installation_id !== 'string') {
291- // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', {
292- // error: 'Missing or invalid installation_id parameter',
293- // }));
294- // }
295-
296- // /**
297- // * Verify state (CSRF protection)
298- // * getState() atomically gets and deletes the state, preventing reuse
299- // */
300- // const stateData = await stateStore.getState(state);
301-
302- // if (!stateData) {
303- // log('warn', `Invalid or expired state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`);
304-
305- // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', {
306- // error: 'Invalid or expired state. Please try connecting again.',
307- // }));
308- // }
309-
310- // const { projectId, userId } = stateData;
311-
312- // log('info', projectId, `Processing callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`);
313-
314- // /**
315- // * Verify project exists
316- // */
317- // const project = await factories.projectsFactory.findById(projectId);
318-
319- // if (!project) {
320- // log('error', projectId, 'Project not found');
321-
322- // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', {
323- // error: `Project not found: ${projectId}`,
324- // }));
325- // }
326-
327- // /**
328- // * Get installation info from GitHub
329- // */
330- // let installation;
331-
332- // try {
333- // installation = await githubService.getInstallationForRepository(installation_id);
334- // log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`);
335- // } catch (error) {
336- // log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`);
337-
338- // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, {
339- // error: 'Failed to retrieve GitHub installation information. Please try again.',
340- // }));
341- // }
342-
343- // /**
344- // * For now, we save only installationId
345- // * repoId and repoFullName will be set when creating the first issue or can be configured later
346- // * GitHub App installation can include multiple repositories, so we don't know which one to use yet
347- // */
348- // const taskManagerConfig = {
349- // type: 'github',
350- // autoTaskEnabled: false,
351- // taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT,
352- // assignAgent: false,
353- // connectedAt: new Date(),
354- // updatedAt: new Date(),
355- // config: {
356- // installationId: installation_id,
357- // repoId: '',
358- // repoFullName: '',
359- // },
360- // };
361-
362- // let successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, {
363- // success: 'true',
364- // });
365-
366- // /**
367- // * Save taskManager configuration to project
368- // */
369- // try {
370- // await project.updateProject(({
371- // taskManager: taskManagerConfig,
372- // }) as any);
373-
374- // log('info', projectId, 'Successfully connected GitHub integration. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen));
375- // } catch (error) {
376- // log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`);
377-
378- // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, {
379- // error: 'Failed to save Task Manager configuration. Please try again.',
380- // }));
381- // }
382-
383- // /**
384- // * Redirect to Garage with success parameter
385- // */
386- // return res.redirect(successRedirectUrl);
387- // } catch (error) {
388- // log('error', 'Error in /callback endpoint:', error);
389- // next(error);
390- // }
391- // });
392-
393272 /**
394273 * GET /integration/github/oauth?code=<code>&state=<state>&installation_id=<installation_id>
395274 * Handle GitHub OAuth callback for user-to-server token
396275 * Also handles GitHub App installation if installation_id is present
397276 */
398277 router . get ( '/oauth' , async ( req , res , next ) => {
399278 try {
279+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
400280 const { code, state, installation_id } = req . query ;
401281
402282 /**
403283 * Log OAuth callback request for debugging
404284 */
285+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
405286 log ( 'info' , `OAuth callback received: state=${ state } , code=${ code ? 'present' : 'missing' } , installation_id=${ installation_id ? 'present' : 'missing' } , query=${ JSON . stringify ( req . query ) } ` ) ;
406287
407288 /**
@@ -454,16 +335,16 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
454335 * If installation_id is present, handle GitHub App installation first
455336 * This happens when "Request user authorization (OAuth) during installation" is enabled
456337 */
338+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
457339 if ( installation_id && typeof installation_id === 'string' ) {
340+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
458341 log ( 'info' , projectId , `GitHub App installation detected (installation_id: ${ installation_id } ), processing installation first` ) ;
459342
460343 /**
461- * Get installation info from GitHub
344+ * Get installation info from GitHub (validates installation exists)
462345 */
463- let installation ;
464-
465346 try {
466- installation = await githubService . getInstallationForRepository ( installation_id ) ;
347+ await githubService . getInstallationForRepository ( installation_id ) ;
467348 log ( 'info' , projectId , `Retrieved installation info for installation_id: ${ sgr ( installation_id , Effect . ForegroundCyan ) } ` ) ;
468349 } catch ( error ) {
469350 log ( 'error' , projectId , `Failed to get installation info: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
@@ -484,6 +365,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
484365 connectedAt : new Date ( ) ,
485366 updatedAt : new Date ( ) ,
486367 config : {
368+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
487369 installationId : installation_id ,
488370 repoId : '' ,
489371 repoFullName : '' ,
@@ -497,10 +379,11 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
497379 ...taskManagerConfig ,
498380 config : {
499381 ...project . taskManager . config ,
382+ // eslint-disable-next-line @typescript-eslint/camelcase, camelcase
500383 installationId : installation_id ,
501384 } ,
502385 } : taskManagerConfig ,
503- } as any ) ;
386+ } as Partial < ProjectDBScheme > ) ;
504387
505388 log ( 'info' , projectId , 'Successfully saved GitHub App installation' ) ;
506389 } catch ( error ) {
@@ -592,7 +475,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
592475 try {
593476 await project . updateProject ( {
594477 taskManager : updatedTaskManager ,
595- } as any ) ;
478+ } as Partial < ProjectDBScheme > ) ;
596479
597480 log ( 'info' , projectId , `Successfully saved delegatedUser token for user ${ sgr ( tokenData . user . login , Effect . ForegroundCyan ) } ` ) ;
598481 } catch ( error ) {
@@ -654,7 +537,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
654537 */
655538 const payload = req . body as Buffer ;
656539 const hmac = createHmac ( 'sha256' , webhookSecret ) ;
657- hmac . update ( payload as any ) ;
540+
541+ hmac . update ( payload . toString ( 'binary' ) , 'binary' ) ;
658542 const calculatedSignature = `sha256=${ hmac . digest ( 'hex' ) } ` ;
659543
660544 /**
@@ -683,10 +567,12 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
683567 /**
684568 * Parse webhook payload
685569 */
686- let payloadData : any ;
570+ type GitHubWebhookPayload = { installation ?: { id ?: number | string } ; action ?: string } ;
571+
572+ let payloadData : GitHubWebhookPayload ;
687573
688574 try {
689- payloadData = JSON . parse ( payload . toString ( ) ) ;
575+ payloadData = JSON . parse ( payload . toString ( ) ) as GitHubWebhookPayload ;
690576 } catch ( error ) {
691577 log ( 'error' , 'Failed to parse webhook payload:' , error ) ;
692578
@@ -797,7 +683,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
797683 /**
798684 * Check if taskManager is configured
799685 */
800- const taskManager = ( project as any ) . taskManager ;
686+ const taskManager = project . taskManager ;
801687
802688 if ( ! taskManager ) {
803689 res . status ( 400 ) . json ( { error : 'Task Manager is not configured for this project' } ) ;
@@ -825,7 +711,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
825711 /**
826712 * Log repository details for debugging
827713 */
828- const repoOwners = [ ...new Set ( repositories . map ( ( r ) => r . fullName . split ( '/' ) [ 0 ] ) ) ] ;
714+ const repoOwners = [ ...new Set ( repositories . map ( ( r ) => r . fullName . split ( '/' ) [ 0 ] ) ) ] ;
715+
829716 log ( 'info' , projectId , `Retrieved ${ repositories . length } repository(ies) for installation ${ installationId } ` ) ;
830717 log ( 'info' , projectId , `Repository owners: ${ repoOwners . join ( ', ' ) } ` ) ;
831718
@@ -884,7 +771,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
884771 /**
885772 * Check if taskManager is configured
886773 */
887- const taskManager = ( project as any ) . taskManager ;
774+ const taskManager = project . taskManager ;
888775
889776 if ( ! taskManager ) {
890777 res . status ( 400 ) . json ( { error : 'Task Manager is not configured for this project' } ) ;
@@ -908,7 +795,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router
908795 try {
909796 await project . updateProject ( {
910797 taskManager : updatedTaskManager ,
911- } as any ) ;
798+ } ) ;
912799
913800 log ( 'info' , validatedProjectId , `Updated repository selection: ${ repoFullName } (${ repoId } )` ) ;
914801
0 commit comments