@@ -14,12 +14,16 @@ import { Config } from "../config";
1414import  {  RepoURL  }  from  "../repohost" ; 
1515import  {  UnauthorizedError  }  from  "../errors" ; 
1616import  {  GitHubOAuthScopes  }  from  "@gitpod/public-api-common/lib/auth-providers" ; 
17+ import  {  containsScopes  }  from  "./token-scopes-inclusion" ; 
18+ import  {  TokenService  }  from  "../user/token-service" ; 
19+ import  {  ApplicationError ,  ErrorCodes  }  from  "@gitpod/gitpod-protocol/lib/messaging/error" ; 
1720
1821@injectable ( ) 
1922export  class  GitHubService  extends  RepositoryService  { 
2023    constructor ( 
2124        @inject ( GitHubRestApi )  protected  readonly  githubApi : GitHubRestApi , 
2225        @inject ( Config )  private  readonly  config : Config , 
26+         @inject ( TokenService )  private  readonly  tokenService : TokenService , 
2327        @inject ( GithubContextParser )  private  readonly  githubContextParser : GithubContextParser , 
2428    )  { 
2529        super ( ) ; 
@@ -44,6 +48,53 @@ export class GitHubService extends RepositoryService {
4448        } 
4549    } 
4650
51+     async  installAutomatedPrebuilds ( user : User ,  cloneUrl : string ) : Promise < void >  { 
52+         const  parsedRepoUrl  =  RepoURL . parseRepoUrl ( cloneUrl ) ; 
53+         if  ( ! parsedRepoUrl )  { 
54+             throw  new  ApplicationError ( ErrorCodes . BAD_REQUEST ,  `Clone URL not parseable.` ) ; 
55+         } 
56+         let  tokenEntry ; 
57+         try  { 
58+             const  {  owner,  repoName : repo  }  =  await  this . githubContextParser . parseURL ( user ,  cloneUrl ) ; 
59+             const  webhooks  =  ( await  this . githubApi . run ( user ,  ( gh )  =>  gh . repos . listWebhooks ( {  owner,  repo } ) ) ) . data ; 
60+             for  ( const  webhook  of  webhooks )  { 
61+                 if  ( webhook . config . url  ===  this . getHookUrl ( ) )  { 
62+                     await  this . githubApi . run ( user ,  ( gh )  => 
63+                         gh . repos . deleteWebhook ( {  owner,  repo,  hook_id : webhook . id  } ) , 
64+                     ) ; 
65+                 } 
66+             } 
67+             tokenEntry  =  await  this . tokenService . createGitpodToken ( user ,  "prebuild" ,  cloneUrl ) ; 
68+             const  config  =  { 
69+                 url : this . getHookUrl ( ) , 
70+                 content_type : "json" , 
71+                 secret : user . id  +  "|"  +  tokenEntry . token . value , 
72+             } ; 
73+             await  this . githubApi . run ( user ,  ( gh )  =>  gh . repos . createWebhook ( {  owner,  repo,  config } ) ) ; 
74+         }  catch  ( error )  { 
75+             // Hint: here we catch all GH API errors to forward them as Unauthorized to FE, 
76+             // eventually that should be done depending on the error code. 
77+             // Also, if user is not connected at all, then the GH API wrapper is throwing 
78+             // the same error type, but with `providerIsConnected: false`. 
79+ 
80+             if  ( GitHubApiError . is ( error ) )  { 
81+                 // TODO check for `error.code` 
82+                 throw  UnauthorizedError . create ( { 
83+                     host : parsedRepoUrl . host , 
84+                     providerType : "GitHub" , 
85+                     repoName : parsedRepoUrl . repo , 
86+                     requiredScopes : GitHubOAuthScopes . Requirements . PRIVATE_REPO , 
87+                     providerIsConnected : true , 
88+                     isMissingScopes : containsScopes ( 
89+                         tokenEntry ?. token . scopes , 
90+                         GitHubOAuthScopes . Requirements . PRIVATE_REPO , 
91+                     ) , 
92+                 } ) ; 
93+             } 
94+             throw  error ; 
95+         } 
96+     } 
97+ 
4798    protected  getHookUrl ( )  { 
4899        return  this . config . hostUrl 
49100            . asPublicServices ( ) 
0 commit comments