@@ -38,11 +38,13 @@ import {
3838 getTaskByUuid as dbGetTaskByUuid ,
3939 updateProjectDiscordSettings as dbUpdateProjectDiscordSettings ,
4040 deleteProject as dbDeleteProject ,
41+ updateProjectWebhookDetails as dbUpdateProjectWebhookDetails ,
4142} from '@/lib/db' ;
4243import { z } from 'zod' ;
4344import { auth } from '@/lib/authEdge' ;
4445import { Octokit } from 'octokit' ;
4546import { Buffer } from 'buffer' ;
47+ import crypto from 'crypto' ;
4648import { generateProjectScaffold , type GenerateProjectScaffoldInput , type GenerateProjectScaffoldOutput } from '@/ai/flows/generate-project-scaffold' ;
4749import { editFileContentWithAI , type EditFileContentAIInput , type EditFileContentAIOutput } from '@/ai/flows/edit-file-content-ai' ;
4850import { sendDiscordNotification } from '@/lib/discord' ;
@@ -1975,7 +1977,7 @@ export async function updateProjectDiscordSettingsAction(
19751977 return { error : "You do not have permission to change Discord settings for this project." } ;
19761978 }
19771979
1978- const updatedProject = await dbUpdateProjectDiscordSettings ( projectUuid , discordWebhookUrl , discordNotificationsEnabled , notifyTasks , notifyMembers , notifyAnnouncements ) ;
1980+ const updatedProject = await dbUpdateProjectDiscordSettings ( projectUuid , discordWebhookUrl , discordNotificationsEnabled , discordNotifyTasks , discordNotifyMembers , discordNotifyAnnouncements ) ;
19791981
19801982 if ( ! updatedProject ) {
19811983 return { error : "Failed to update project settings in the database." } ;
@@ -2024,3 +2026,87 @@ export async function deleteProjectAction(
20242026 return { error : error . message || "An unexpected error occurred." } ;
20252027 }
20262028}
2029+
2030+ export interface SetupGithubWebhookFormState {
2031+ success ?: boolean ;
2032+ message ?: string ;
2033+ error ?: string ;
2034+ project ?: Project ;
2035+ }
2036+
2037+ export async function setupGithubWebhookAction (
2038+ prevState : SetupGithubWebhookFormState ,
2039+ formData : FormData
2040+ ) : Promise < SetupGithubWebhookFormState > {
2041+ const session = await auth ( ) ;
2042+ if ( ! session ?. user ?. uuid ) {
2043+ return { error : "Authentication required." } ;
2044+ }
2045+
2046+ const projectUuid = formData . get ( 'projectUuid' ) as string ;
2047+ if ( ! projectUuid ) {
2048+ return { error : "Project UUID is required." } ;
2049+ }
2050+
2051+ try {
2052+ const userRole = await dbGetProjectMemberRole ( projectUuid , session . user . uuid ) ;
2053+ if ( ! userRole || ! [ 'owner' , 'co-owner' ] . includes ( userRole ) ) {
2054+ return { error : "You do not have permission to set up webhooks for this project." } ;
2055+ }
2056+
2057+ const project = await dbGetProjectByUuid ( projectUuid ) ;
2058+ if ( ! project || ! project . githubRepoName ) {
2059+ return { error : "Project not found or not linked to GitHub." } ;
2060+ }
2061+ if ( project . githubWebhookId ) {
2062+ return { error : "A webhook is already configured for this project." } ;
2063+ }
2064+
2065+ const oauthToken = await dbGetUserGithubOAuthToken ( session . user . uuid ) ;
2066+ if ( ! oauthToken ?. accessToken ) {
2067+ return { error : "GitHub OAuth token not found. Please connect your account." } ;
2068+ }
2069+
2070+ const [ owner , repo ] = project . githubRepoName . split ( '/' ) ;
2071+ if ( ! owner || ! repo ) return { error : "Invalid GitHub repository name format." } ;
2072+
2073+ const octokit = new Octokit ( { auth : oauthToken . accessToken } ) ;
2074+ const webhookSecret = crypto . randomBytes ( 20 ) . toString ( 'hex' ) ;
2075+ const webhookUrl = `${ process . env . NEXT_PUBLIC_APP_URL } /api/webhooks/github` ;
2076+
2077+ const { data : newWebhook } = await octokit . rest . repos . createWebhook ( {
2078+ owner,
2079+ repo,
2080+ config : {
2081+ url : webhookUrl ,
2082+ content_type : 'json' ,
2083+ secret : webhookSecret ,
2084+ } ,
2085+ events : [ 'push' , 'pull_request' , 'issues' ] ,
2086+ active : true ,
2087+ } ) ;
2088+
2089+ const updatedProject = await dbUpdateProjectWebhookDetails ( project . uuid , newWebhook . id , webhookSecret ) ;
2090+ if ( ! updatedProject ) {
2091+ return { error : "Webhook created on GitHub, but failed to save details in FlowUp." } ;
2092+ }
2093+
2094+ return { success : true , message : `Webhook (ID: ${ newWebhook . id } ) successfully created on GitHub!` , project : updatedProject } ;
2095+
2096+ } catch ( error : any ) {
2097+ console . error ( "Error setting up GitHub webhook:" , error ) ;
2098+ let errorMessage = error . message || "An unexpected error occurred." ;
2099+ if ( error . status === 422 ) { // Unprocessable Entity
2100+ if ( error . message . includes ( "Hook already exists" ) ) {
2101+ errorMessage = "A webhook for this URL already exists on the repository. Please check your repository settings on GitHub." ;
2102+ } else {
2103+ errorMessage = `Could not create webhook (422): ${ error . message } . Check permissions and configuration.` ;
2104+ }
2105+ } else if ( error . status === 404 ) {
2106+ errorMessage = "Repository not found. Check that the linked repository exists and you have access." ;
2107+ }
2108+ return { error : errorMessage } ;
2109+ }
2110+ }
2111+
2112+
0 commit comments