44import  hashlib 
55import  requests 
66from  msal  import  ConfidentialClientApplication 
7+ from  datetime  import  datetime 
78
89# --- Determine mode --- 
910TEST_MODE  =  os .getenv ("TEST_MODE" , "true" ).lower () ==  "true" 
1011
1112# Only import Flask if not in TEST_MODE 
1213if  not  TEST_MODE :
13-     from  flask  import  Flask , request ,  jsonify 
14+     from  flask  import  Flask , request 
1415    app  =  Flask (__name__ )
1516
1617# --- Microsoft Graph Email Sending Function --- 
@@ -23,7 +24,7 @@ def send_email_via_graph(subject, body):
2324
2425    if  not  all ([TENANT_ID , CLIENT_ID , CLIENT_SECRET , FROM_EMAIL , TO_EMAIL ]):
2526        print ("❌ Missing required environment variables" )
26-         return   False 
27+         return 
2728
2829    try :
2930        app_msal  =  ConfidentialClientApplication (
@@ -35,7 +36,7 @@ def send_email_via_graph(subject, body):
3536        access_token  =  token .get ("access_token" )
3637        if  not  access_token :
3738            print (f"❌ Failed to get access token: { token }  " )
38-             return   False 
39+             return 
3940
4041        email_msg  =  {
4142            "message" : {
@@ -53,14 +54,22 @@ def send_email_via_graph(subject, body):
5354
5455        if  response .status_code  ==  202 :
5556            print (f"✅ Email sent to { TO_EMAIL }  " )
56-             return  True 
5757        else :
5858            print (f"❌ Failed to send email: { response .status_code }   { response .text }  " )
59-             return  False 
6059
6160    except  Exception  as  e :
6261        print (f"❌ Exception occurred while sending email: { e }  " )
63-         return  False 
62+ 
63+ # --- Format GitHub timestamp --- 
64+ def  format_timestamp (ts ):
65+     """Convert GitHub timestamp to 'YYYY-MM-DD HH:MM:SS' format.""" 
66+     if  not  ts :
67+         return  None 
68+     try :
69+         dt  =  datetime .strptime (ts , "%Y-%m-%dT%H:%M:%SZ" )
70+         return  dt .strftime ("%Y-%m-%d %H:%M:%S" )
71+     except  Exception :
72+         return  ts   # fallback 
6473
6574# --- Verify GitHub webhook signature --- 
6675def  verify_github_signature (payload_body , signature , secret ):
@@ -75,13 +84,13 @@ def verify_github_signature(payload_body, signature, secret):
7584    expected_signature  =  "sha256="  +  mac .hexdigest ()
7685    return  hmac .compare_digest (expected_signature , signature )
7786
78- # --- GitHub Webhook Handler  --- 
87+ # --- GitHub Webhook + Health Handlers  --- 
7988if  not  TEST_MODE :
8089    @app .route ("/webhook" , methods = ["POST" ]) 
8190    def  github_webhook ():
8291        payload_body  =  request .data 
8392        signature  =  request .headers .get ("X-Hub-Signature-256" )
84-         secret  =  os .getenv ("GITHUB_WEBHOOK_SECRET" )   # ✅ Correct usage 
93+         secret  =  os .getenv ("GITHUB_WEBHOOK_SECRET" )
8594
8695        # Debug logging 
8796        print ("📥 Incoming GitHub webhook" )
@@ -102,19 +111,35 @@ def github_webhook():
102111
103112        event  =  request .headers .get ("X-GitHub-Event" , "" )
104113
105-         # Handle repository create/delete events 
106114        if  event  ==  "repository"  and  data .get ("action" ) in  ["created" , "deleted" ]:
107-             repo_name  =  data [ "repository" ][ "full_name" ] 
115+             repo  =  data . get ( "repository" , {}) 
108116            action  =  data ["action" ]
109117
110-             subject  =  f"[GitHub Alert] Repository { action }  : { repo_name }  " 
111-             body  =  (
112-                 f"A repository was { action }   in your GitHub organization.\n \n " 
113-                 f"Details:\n { json .dumps (data , indent = 2 )}  " 
114-             )
118+             repo_name  =  repo .get ("name" )
119+             full_name  =  repo .get ("full_name" )
120+             org  =  repo .get ("owner" , {}).get ("login" )
121+             owner_id  =  repo .get ("owner" , {}).get ("id" )
122+             default_branch  =  repo .get ("default_branch" )
123+             created_at  =  format_timestamp (repo .get ("created_at" ))
124+             updated_at  =  format_timestamp (repo .get ("updated_at" ))
125+             html_url  =  repo .get ("html_url" )
126+ 
127+             subject  =  f"[GitHub Alert] Repository { action }  : { full_name }  " 
128+             body  =  f""" 
129+ A repository was { action }   in your GitHub organization. 
130+ 
131+ Repository: { repo_name }  
132+ Full name: { full_name }  
133+ Organization: { org }  
134+ Owner ID: { owner_id }  
135+ Default branch: { default_branch }  
136+ Created at: { created_at }  
137+ Last updated: { updated_at }  
138+ URL: { html_url }  
139+ """ 
115140
116141            print (f"📩 Sending email alert: { subject }  " )
117-             send_email_via_graph (subject , body )
142+             send_email_via_graph (subject , body . strip () )
118143        else :
119144            print (f"ℹ️ Ignored event: { event }  , action: { data .get ('action' )}  " )
120145
@@ -123,18 +148,16 @@ def github_webhook():
123148    # Health check endpoint 
124149    @app .route ("/health" , methods = ["GET" ]) 
125150    def  health_check ():
126-         return  jsonify ( {"status" : "running" }) , 200 
151+         return  {"status" : "running" }, 200 
127152
128153# --- Main Entry Point --- 
129154if  __name__  ==  "__main__" :
130155    if  TEST_MODE :
131156        print ("🔹 TEST_MODE: sending test email" )
132157        send_email_via_graph (
133158            "[Test] Graph Email" ,
134-             "This is a test email sent via Microsoft Graph with application permissions. " 
159+             "The information of Quantori's GitHub repositories has been updated " 
135160        )
136161    else :
137162        print ("✅ Flask is up and listening on /webhook and /health" )
138-         # Azure expects port 8000 by default for containerized apps 
139-         port  =  int (os .getenv ("PORT" , 8000 ))
140-         app .run (host = "0.0.0.0" , port = port )
163+         app .run (host = "0.0.0.0" , port = int (os .getenv ("PORT" , 8000 )))
0 commit comments