ak0601 commited on
Commit
92a00c6
·
verified ·
1 Parent(s): ed6d52b

Update app_hug.py

Browse files
Files changed (1) hide show
  1. app_hug.py +663 -7
app_hug.py CHANGED
@@ -1,4 +1,617 @@
1
- from fastapi import FastAPI, HTTPException, BackgroundTasks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from pydantic import BaseModel, EmailStr, Field
3
  from typing import Optional, Tuple
4
  from enum import Enum
@@ -21,6 +634,32 @@ import json
21
  # Load environment variables (not needed on Hugging Face, but harmless)
22
  load_dotenv()
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ------------------------------------------
25
  # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
26
  # ------------------------------------------
@@ -42,6 +681,13 @@ def ensure_credentials_file():
42
  # FastAPI app
43
  # ------------------------------------------
44
  app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
 
 
 
 
 
 
 
45
 
46
  SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
47
  openai_api_key = os.getenv("OPENAI_API_KEY")
@@ -298,6 +944,7 @@ def generate_recruitment_message_with_subject(
298
  - Explains why this opportunity aligns with their background
299
  - Suggests next steps (like a call or meeting)
300
  - Maintains a warm, professional tone
 
301
  """
302
  else: # followup
303
  prompt += """
@@ -393,20 +1040,22 @@ def format_email_html(body: str, sender_name: Optional[str]=None, sender_org: Op
393
  # ------------------------------------------
394
  @app.get("/")
395
  async def root():
 
396
  return {
397
  "message": "Recruitment Message Generator API",
398
  "version": "1.0.0",
 
 
399
  "endpoints": [
400
  "/generate-message",
401
  "/refine-message",
402
  "/authenticate",
403
  "/send-message",
404
  "/docs"
405
-
406
  ]
407
  }
408
 
409
- @app.post("/send-message", response_model=MessageResponse)
410
  async def send_message(request: SendMessageRequest):
411
  try:
412
  # Authenticate sender
@@ -455,7 +1104,7 @@ async def send_message(request: SendMessageRequest):
455
  error=str(e)
456
  )
457
 
458
- @app.post("/generate-message", response_model=MessageResponse)
459
  async def generate_message(request: GenerateMessageRequest, background_tasks: BackgroundTasks):
460
  try:
461
  current_position = f"{request.current_role} at {request.current_company}"
@@ -529,7 +1178,7 @@ async def generate_message(request: GenerateMessageRequest, background_tasks: Ba
529
  error=str(e)
530
  )
531
 
532
- @app.post("/refine-message", response_model=MessageResponse)
533
  async def refine_message(request: FeedbackRequest):
534
  try:
535
  email_subject, refined_message = refine_message_with_feedback(
@@ -549,7 +1198,7 @@ async def refine_message(request: FeedbackRequest):
549
  error=str(e)
550
  )
551
 
552
- @app.post("/authenticate", response_model=AuthenticateResponse)
553
  async def authenticate_user(request: AuthenticateRequest):
554
  try:
555
  if check_user_token_exists(request.email):
@@ -601,8 +1250,15 @@ async def authenticate_user(request: AuthenticateRequest):
601
 
602
  @app.get("/health")
603
  async def health_check():
 
604
  return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
605
 
 
 
 
 
 
 
606
  if __name__ == "__main__":
607
  import uvicorn
608
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ # from fastapi import FastAPI, HTTPException, BackgroundTasks
2
+ # from pydantic import BaseModel, EmailStr, Field
3
+ # from typing import Optional, Tuple
4
+ # from enum import Enum
5
+ # import os
6
+ # import base64
7
+ # import pickle
8
+ # import pandas as pd
9
+ # from dotenv import load_dotenv
10
+ # from langchain_openai import ChatOpenAI
11
+ # from langchain.schema import HumanMessage, SystemMessage
12
+ # from email.mime.text import MIMEText
13
+ # from google.auth.transport.requests import Request
14
+ # from google.oauth2.credentials import Credentials
15
+ # from google_auth_oauthlib.flow import InstalledAppFlow
16
+ # from googleapiclient.discovery import build
17
+ # from googleapiclient.errors import HttpError
18
+ # from datetime import datetime
19
+ # import json
20
+
21
+ # # Load environment variables (not needed on Hugging Face, but harmless)
22
+ # load_dotenv()
23
+
24
+ # # ------------------------------------------
25
+ # # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
26
+ # # ------------------------------------------
27
+ # def ensure_credentials_file():
28
+ # credentials_env = os.getenv("GOOGLE_CREDENTIALS_JSON")
29
+ # credentials_path = "credentials_SYNAPSE.json"
30
+ # if not os.path.exists(credentials_path):
31
+ # if not credentials_env:
32
+ # raise Exception("GOOGLE_CREDENTIALS_JSON not found in environment variables.")
33
+ # try:
34
+ # parsed_json = json.loads(credentials_env)
35
+ # except json.JSONDecodeError:
36
+ # raise Exception("Invalid JSON in GOOGLE_CREDENTIALS_JSON")
37
+ # with open(credentials_path, "w") as f:
38
+ # json.dump(parsed_json, f, indent=2)
39
+ # return credentials_path
40
+
41
+ # # ------------------------------------------
42
+ # # FastAPI app
43
+ # # ------------------------------------------
44
+ # app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
45
+
46
+ # SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
47
+ # openai_api_key = os.getenv("OPENAI_API_KEY")
48
+
49
+ # # ------------------------------------------
50
+ # # Enums and Models
51
+ # # ------------------------------------------
52
+ # class MessageType(str, Enum):
53
+ # OUTREACH = "outreach"
54
+ # INTRODUCTORY = "introductory"
55
+ # FOLLOWUP = "followup"
56
+
57
+ # class GenerateMessageRequest(BaseModel):
58
+ # job_evaluation: Optional[str] = Field(None, description="(Optional) Recruiter's evaluation of candidate for the job")
59
+ # sender_email: EmailStr
60
+ # reply_to_email: Optional[EmailStr] = Field(None, description="Recruiter's email for reply-to header")
61
+ # recipient_email: EmailStr
62
+ # candidate_name: str
63
+ # current_role: str
64
+ # current_company: str
65
+ # company_name: str
66
+ # role: str
67
+ # recruiter_name: str
68
+ # organisation: str
69
+ # message_type: MessageType
70
+ # send_email: bool = False
71
+ # past_conversation: Optional[str] = Field(None, description="(Optional) Previous messages with candidate")
72
+
73
+ # class FeedbackRequest(BaseModel):
74
+ # message: str
75
+ # feedback: str
76
+
77
+ # class AuthenticateRequest(BaseModel):
78
+ # email: EmailStr
79
+
80
+ # class AuthenticateResponse(BaseModel):
81
+ # success: bool
82
+ # message: str
83
+ # error: Optional[str] = None
84
+
85
+ # class MessageResponse(BaseModel):
86
+ # success: bool
87
+ # message: str
88
+ # email_sent: bool = False
89
+ # email_subject: Optional[str] = None
90
+ # error: Optional[str] = None
91
+
92
+ # class SendMessageRequest(BaseModel):
93
+ # subject: str
94
+ # email_body: str
95
+ # sender_email: EmailStr
96
+ # recipient_email: EmailStr
97
+ # reply_to_email: Optional[EmailStr] = None
98
+
99
+ # # ------------------------------------------
100
+ # # Gmail Helper Functions
101
+ # # ------------------------------------------
102
+ # def get_token_file_path(email: str) -> str:
103
+ # tokens_dir = "gmail_tokens"
104
+ # if not os.path.exists(tokens_dir):
105
+ # os.makedirs(tokens_dir)
106
+ # safe_email = email.replace("@", "_at_").replace(".", "_dot_")
107
+ # return os.path.join(tokens_dir, f"token_{safe_email}.pickle")
108
+
109
+ # def check_user_token_exists(email: str) -> bool:
110
+ # token_file = get_token_file_path(email)
111
+ # return os.path.exists(token_file)
112
+
113
+ # def load_user_credentials(email: str):
114
+ # token_file = get_token_file_path(email)
115
+ # if os.path.exists(token_file):
116
+ # try:
117
+ # with open(token_file, 'rb') as token:
118
+ # creds = pickle.load(token)
119
+ # return creds
120
+ # except Exception:
121
+ # if os.path.exists(token_file):
122
+ # os.remove(token_file)
123
+ # return None
124
+
125
+ # def save_user_credentials(email: str, creds):
126
+ # token_file = get_token_file_path(email)
127
+ # with open(token_file, 'wb') as token:
128
+ # pickle.dump(creds, token)
129
+
130
+ # def create_new_credentials(email: str):
131
+ # credentials_path = ensure_credentials_file()
132
+ # flow = InstalledAppFlow.from_client_secrets_file(
133
+ # credentials_path, SCOPES
134
+ # )
135
+ # creds = flow.run_local_server(port=0)
136
+ # save_user_credentials(email, creds)
137
+ # return creds
138
+
139
+ # def authenticate_gmail(email: str, create_if_missing: bool = False):
140
+ # creds = load_user_credentials(email)
141
+ # if creds:
142
+ # if creds.expired and creds.refresh_token:
143
+ # try:
144
+ # creds.refresh(Request())
145
+ # save_user_credentials(email, creds)
146
+ # except Exception:
147
+ # if create_if_missing:
148
+ # try:
149
+ # creds = create_new_credentials(email)
150
+ # except:
151
+ # return None
152
+ # else:
153
+ # return None
154
+ # elif not creds.valid:
155
+ # creds = None
156
+ # if not creds:
157
+ # if create_if_missing:
158
+ # try:
159
+ # creds = create_new_credentials(email)
160
+ # except:
161
+ # return None
162
+ # else:
163
+ # return None
164
+ # try:
165
+ # service = build("gmail", "v1", credentials=creds)
166
+ # return service
167
+ # except Exception:
168
+ # return None
169
+
170
+ # from email.mime.text import MIMEText
171
+
172
+ # def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None, is_html: bool = False):
173
+ # if is_html:
174
+ # message = MIMEText(message_text, "html")
175
+ # else:
176
+ # message = MIMEText(message_text, "plain")
177
+ # message["To"] = to
178
+ # message["From"] = sender
179
+ # message["Subject"] = subject
180
+ # if reply_to:
181
+ # message["Reply-To"] = reply_to
182
+ # raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
183
+ # return {"raw": raw_message}
184
+
185
+ # def send_gmail_message(service, user_id: str, message: dict):
186
+ # try:
187
+ # result = service.users().messages().send(userId=user_id, body=message).execute()
188
+ # return result is not None
189
+ # except HttpError:
190
+ # return False
191
+
192
+ # # ------------------------------------------
193
+ # # LLM (OpenAI) Message Generation Helpers
194
+ # # ------------------------------------------
195
+ # def refine_message_with_feedback(
196
+ # original_message: str,
197
+ # feedback: str,
198
+ # ) -> Tuple[str, str]:
199
+ # api_key = os.getenv("OPENAI_API_KEY")
200
+ # llm = ChatOpenAI(
201
+ # model="gpt-4o-mini",
202
+ # temperature=0.7,
203
+ # max_tokens=600,
204
+ # openai_api_key=api_key
205
+ # )
206
+ # prompt = f"""
207
+ # Please refine the following recruitment message based on the provided feedback:
208
+
209
+ # ORIGINAL MESSAGE:
210
+ # {original_message}
211
+
212
+ # FEEDBACK:
213
+ # {feedback}
214
+
215
+ # Please provide your response in the following format:
216
+ # SUBJECT: [Your subject line here]
217
+
218
+ # BODY:
219
+ # [Your refined email body content here]
220
+ # Keep the same tone and intent as the original message, but incorporate the feedback to improve it.
221
+ # """
222
+ # try:
223
+ # messages = [
224
+ # SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
225
+ # HumanMessage(content=prompt)
226
+ # ]
227
+ # response = llm.invoke(messages)
228
+ # content = response.content.strip()
229
+ # subject_line = ""
230
+ # body_content = ""
231
+ # lines = content.split('\n')
232
+ # body_found = False
233
+ # body_lines = []
234
+ # for line in lines:
235
+ # if line.strip().startswith('SUBJECT:'):
236
+ # subject_line = line.replace('SUBJECT:', '').strip()
237
+ # elif line.strip().startswith('BODY:'):
238
+ # body_found = True
239
+ # elif body_found and line.strip():
240
+ # body_lines.append(line)
241
+ # body_content = '\n'.join(body_lines).strip()
242
+ # if not subject_line:
243
+ # subject_line = "Recruitment Opportunity - Updated"
244
+ # if not body_content:
245
+ # body_content = content
246
+ # return subject_line, body_content
247
+ # except Exception as e:
248
+ # raise HTTPException(status_code=500, detail=f"Error refining message: {str(e)}")
249
+
250
+ # def generate_recruitment_message_with_subject(
251
+ # msg_type: str,
252
+ # company: str,
253
+ # role_title: str,
254
+ # recruiter: str,
255
+ # org: str,
256
+ # candidate: str,
257
+ # current_pos: str,
258
+ # evaluation: Optional[str] = None,
259
+ # feedback: Optional[str] = None,
260
+ # past_conversation: Optional[str] = None
261
+ # ) -> Tuple[str, str]:
262
+ # api_key = os.getenv("OPENAI_API_KEY")
263
+ # llm = ChatOpenAI(
264
+ # model="gpt-4o-mini",
265
+ # temperature=0.7,
266
+ # max_tokens=600,
267
+ # openai_api_key=api_key
268
+ # )
269
+ # # Outreach: Only request consent
270
+ # if msg_type == "outreach":
271
+ # prompt = f"""
272
+ # Generate a professional initial outreach message to a candidate.
273
+ # - Introduce yourself as {recruiter} from {org}
274
+ # - Clearly state you are reaching out about an open role ({role_title}) at {company}
275
+ # - Ask if they are open to learning more or interested in a quick chat.
276
+ # - Do NOT discuss any job evaluation or judgment.
277
+ # - Explicitly request their consent to share more details. E.g., 'Would you be open to hearing more about this opportunity?'
278
+ # - Keep it short and friendly.
279
+ # - No placeholders like [Candidate Name] or [Role Title] in the output.
280
+ # """
281
+ # else:
282
+ # base_prompt = f"""
283
+ # Generate a professional recruitment {msg_type} with the following details:
284
+ # - Company hiring: {company}
285
+ # - Role: {role_title}
286
+ # - Recruiter: {recruiter} from {org}
287
+ # - Candidate: {candidate}
288
+ # - Candidate's current position: {current_pos}
289
+ # """
290
+ # if evaluation:
291
+ # base_prompt += f"- Evaluation: {evaluation}\n"
292
+ # prompt = base_prompt
293
+ # if msg_type == "introductory":
294
+ # prompt += """
295
+ # Create an introductory message that:
296
+ # - Thanks the candidate for their initial response
297
+ # - Provides more details about the role and company
298
+ # - Explains why this opportunity aligns with their background
299
+ # - Suggests next steps (like a call or meeting)
300
+ # - Maintains a warm, professional tone
301
+ # """
302
+ # else: # followup
303
+ # prompt += """
304
+ # Create a follow-up message that:
305
+ # - References previous communication
306
+ # - Reiterates interest in the candidate
307
+ # - Addresses any potential concerns
308
+ # - Provides additional compelling reasons to consider the role
309
+ # - Includes a clear call to action
310
+ # """
311
+
312
+ # # Use feedback if provided
313
+ # if feedback:
314
+ # prompt += f"\n\nPlease modify the message based on this feedback: {feedback}"
315
+
316
+ # # Use past conversation if provided
317
+ # if past_conversation:
318
+ # prompt += f"""
319
+ # Use the following past conversation as context to inform your reply:
320
+
321
+ # PAST CONVERSATION:
322
+ # {past_conversation}
323
+
324
+ # Write a reply message to the candidate, maintaining professionalism and continuity.
325
+ # """
326
+
327
+ # prompt += """
328
+
329
+ # Please provide your response in the following format:
330
+ # SUBJECT: [Your subject line here]
331
+
332
+ # BODY:
333
+ # [Your email body content here]
334
+ # """
335
+
336
+ # try:
337
+ # messages = [
338
+ # SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
339
+ # HumanMessage(content=prompt)
340
+ # ]
341
+ # response = llm.invoke(messages)
342
+ # content = response.content.strip()
343
+ # subject_line = ""
344
+ # body_content = ""
345
+ # lines = content.split('\n')
346
+ # body_found = False
347
+ # body_lines = []
348
+ # for line in lines:
349
+ # if line.strip().startswith('SUBJECT:'):
350
+ # subject_line = line.replace('SUBJECT:', '').strip()
351
+ # elif line.strip().startswith('BODY:'):
352
+ # body_found = True
353
+ # elif body_found and line.strip():
354
+ # body_lines.append(line)
355
+ # body_content = '\n'.join(body_lines).strip()
356
+ # if not subject_line:
357
+ # subject_line = f"Opportunity at {company} - {role_title}"
358
+ # if not body_content:
359
+ # body_content = content
360
+ # return subject_line, body_content
361
+ # except Exception as e:
362
+ # raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
363
+
364
+
365
+ # def format_email_html(body: str, sender_name: Optional[str]=None, sender_org: Optional[str]=None):
366
+ # """Wrap plain text body in an HTML template for better appearance."""
367
+ # # Convert consecutive line breaks into HTML paragraphs
368
+ # import re
369
+ # # Smart paragraphing
370
+ # body = re.sub(r"\n\s*\n", "</p><p>", body.strip()) # Double newlines => new paragraph
371
+ # body = re.sub(r"\n", "<br>", body) # Single newlines => <br>
372
+ # # Optional signature
373
+ # signature = ""
374
+ # if sender_name or sender_org:
375
+ # signature = "<br><br>Best regards,<br>"
376
+ # if sender_name:
377
+ # signature += f"{sender_name}<br>"
378
+ # if sender_org:
379
+ # signature += f"{sender_org}"
380
+ # html = f"""
381
+ # <html>
382
+ # <body style="font-family: Arial, sans-serif; color: #222; line-height: 1.7; max-width: 540px;">
383
+ # <p>{body}</p>
384
+ # {signature}
385
+ # </body>
386
+ # </html>
387
+ # """
388
+ # return html
389
+
390
+
391
+ # # ------------------------------------------
392
+ # # FastAPI Endpoints
393
+ # # ------------------------------------------
394
+ # @app.get("/")
395
+ # async def root():
396
+ # return {
397
+ # "message": "Recruitment Message Generator API",
398
+ # "version": "1.0.0",
399
+ # "endpoints": [
400
+ # "/generate-message",
401
+ # "/refine-message",
402
+ # "/authenticate",
403
+ # "/send-message",
404
+ # "/docs"
405
+
406
+ # ]
407
+ # }
408
+
409
+ # @app.post("/send-message", response_model=MessageResponse)
410
+ # async def send_message(request: SendMessageRequest):
411
+ # try:
412
+ # # Authenticate sender
413
+ # service = authenticate_gmail(request.sender_email)
414
+ # if not service:
415
+ # return MessageResponse(
416
+ # success=False,
417
+ # message="",
418
+ # error="Gmail authentication failed"
419
+ # )
420
+
421
+
422
+ # formatted_html = format_email_html(
423
+ # request.email_body
424
+ # )
425
+ # # Create the email
426
+ # email_message = create_email_message(
427
+ # sender=request.sender_email,
428
+ # to=request.recipient_email,
429
+ # subject=request.subject,
430
+ # message_text=formatted_html,
431
+ # reply_to=request.reply_to_email,
432
+ # is_html=True
433
+ # )
434
+ # # Send the email
435
+ # email_sent = send_gmail_message(service, "me", email_message)
436
+ # if email_sent:
437
+ # return MessageResponse(
438
+ # success=True,
439
+ # message="Email sent successfully.",
440
+ # email_sent=True,
441
+ # email_subject=request.subject
442
+ # )
443
+ # else:
444
+ # return MessageResponse(
445
+ # success=False,
446
+ # message="",
447
+ # email_sent=False,
448
+ # email_subject=request.subject,
449
+ # error="Failed to send via Gmail API"
450
+ # )
451
+ # except Exception as e:
452
+ # return MessageResponse(
453
+ # success=False,
454
+ # message="",
455
+ # error=str(e)
456
+ # )
457
+
458
+ # @app.post("/generate-message", response_model=MessageResponse)
459
+ # async def generate_message(request: GenerateMessageRequest, background_tasks: BackgroundTasks):
460
+ # try:
461
+ # current_position = f"{request.current_role} at {request.current_company}"
462
+ # email_subject, generated_message = generate_recruitment_message_with_subject(
463
+ # msg_type=request.message_type.value.replace('followup', 'follow-up'),
464
+ # company=request.company_name,
465
+ # role_title=request.role,
466
+ # recruiter=request.recruiter_name,
467
+ # org=request.organisation,
468
+ # candidate=request.candidate_name,
469
+ # current_pos=current_position,
470
+ # evaluation=request.job_evaluation,
471
+ # past_conversation=request.past_conversation
472
+ # )
473
+ # email_sent = False
474
+ # if request.send_email:
475
+ # registered_users = []
476
+ # if os.path.exists("registered_users.csv"):
477
+ # df = pd.read_csv("registered_users.csv")
478
+ # registered_users = df['email'].tolist() if 'email' in df.columns else []
479
+ # if request.sender_email.lower() not in [user.lower() for user in registered_users]:
480
+ # return MessageResponse(
481
+ # success=True,
482
+ # message=generated_message,
483
+ # email_sent=False,
484
+ # email_subject=email_subject,
485
+ # error="Email not sent: Sender email is not registered"
486
+ # )
487
+ # service = authenticate_gmail(request.sender_email)
488
+ # if service:
489
+ # formatted_html = format_email_html(
490
+ # generated_message,
491
+ # sender_name=request.recruiter_name,
492
+ # sender_org=request.organisation
493
+ # )
494
+ # email_message = create_email_message(
495
+ # sender=request.sender_email,
496
+ # to=request.recipient_email,
497
+ # subject=email_subject,
498
+ # message_text=formatted_html,
499
+ # reply_to=request.reply_to_email,
500
+ # is_html=True
501
+ # )
502
+ # email_sent = send_gmail_message(service, "me", email_message)
503
+ # if not email_sent:
504
+ # return MessageResponse(
505
+ # success=True,
506
+ # message=generated_message,
507
+ # email_sent=False,
508
+ # email_subject=email_subject,
509
+ # error="Email not sent: Failed to send via Gmail API"
510
+ # )
511
+ # else:
512
+ # return MessageResponse(
513
+ # success=True,
514
+ # message=generated_message,
515
+ # email_sent=False,
516
+ # email_subject=email_subject,
517
+ # error="Email not sent: Gmail authentication failed"
518
+ # )
519
+ # return MessageResponse(
520
+ # success=True,
521
+ # message=generated_message,
522
+ # email_sent=email_sent,
523
+ # email_subject=email_subject
524
+ # )
525
+ # except Exception as e:
526
+ # return MessageResponse(
527
+ # success=False,
528
+ # message="",
529
+ # error=str(e)
530
+ # )
531
+
532
+ # @app.post("/refine-message", response_model=MessageResponse)
533
+ # async def refine_message(request: FeedbackRequest):
534
+ # try:
535
+ # email_subject, refined_message = refine_message_with_feedback(
536
+ # original_message=request.message,
537
+ # feedback=request.feedback
538
+ # )
539
+ # return MessageResponse(
540
+ # success=True,
541
+ # message=refined_message,
542
+ # email_sent=False,
543
+ # email_subject=email_subject
544
+ # )
545
+ # except Exception as e:
546
+ # return MessageResponse(
547
+ # success=False,
548
+ # message="",
549
+ # error=str(e)
550
+ # )
551
+
552
+ # @app.post("/authenticate", response_model=AuthenticateResponse)
553
+ # async def authenticate_user(request: AuthenticateRequest):
554
+ # try:
555
+ # if check_user_token_exists(request.email):
556
+ # service = authenticate_gmail(request.email, create_if_missing=False)
557
+ # if service:
558
+ # return AuthenticateResponse(
559
+ # success=True,
560
+ # message="User already authenticated and token is valid"
561
+ # )
562
+ # else:
563
+ # service = authenticate_gmail(request.email, create_if_missing=True)
564
+ # if service:
565
+ # return AuthenticateResponse(
566
+ # success=True,
567
+ # message="Token refreshed successfully"
568
+ # )
569
+ # else:
570
+ # return AuthenticateResponse(
571
+ # success=False,
572
+ # message="Failed to refresh token",
573
+ # error="Could not refresh existing token. Please check credentials.json"
574
+ # )
575
+ # else:
576
+ # try:
577
+ # creds = create_new_credentials(request.email)
578
+ # if creds:
579
+ # return AuthenticateResponse(
580
+ # success=True,
581
+ # message="Authentication successful. Token created and saved."
582
+ # )
583
+ # else:
584
+ # return AuthenticateResponse(
585
+ # success=False,
586
+ # message="Authentication failed",
587
+ # error="Failed to create credentials"
588
+ # )
589
+ # except Exception as e:
590
+ # return AuthenticateResponse(
591
+ # success=False,
592
+ # message="Authentication failed",
593
+ # error=str(e)
594
+ # )
595
+ # except Exception as e:
596
+ # return AuthenticateResponse(
597
+ # success=False,
598
+ # message="Authentication error",
599
+ # error=str(e)
600
+ # )
601
+
602
+ # @app.get("/health")
603
+ # async def health_check():
604
+ # return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
605
+
606
+ # if __name__ == "__main__":
607
+ # import uvicorn
608
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
609
+
610
+
611
+
612
+ from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status
613
+ from fastapi.middleware.cors import CORSMiddleware
614
+ from fastapi.security import APIKeyHeader
615
  from pydantic import BaseModel, EmailStr, Field
616
  from typing import Optional, Tuple
617
  from enum import Enum
 
634
  # Load environment variables (not needed on Hugging Face, but harmless)
635
  load_dotenv()
636
 
637
+ # ------------------------------------------
638
+ # Security Configuration
639
+ # ------------------------------------------
640
+ API_PASSWORD = os.getenv("API_PASSWORD", "Synapse@2025") # Can be overridden by env var
641
+ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
642
+
643
+ def verify_api_key(api_key: Optional[str] = Depends(api_key_header)) -> str:
644
+ """
645
+ Verify the API key/password provided in the request header.
646
+ """
647
+ if api_key is None:
648
+ raise HTTPException(
649
+ status_code=status.HTTP_401_UNAUTHORIZED,
650
+ detail="API Key required. Please provide X-API-Key header.",
651
+ headers={"WWW-Authenticate": "ApiKey"},
652
+ )
653
+
654
+ if api_key != API_PASSWORD:
655
+ raise HTTPException(
656
+ status_code=status.HTTP_401_UNAUTHORIZED,
657
+ detail="Invalid API Key",
658
+ headers={"WWW-Authenticate": "ApiKey"},
659
+ )
660
+
661
+ return api_key
662
+
663
  # ------------------------------------------
664
  # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
665
  # ------------------------------------------
 
681
  # FastAPI app
682
  # ------------------------------------------
683
  app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
684
+ app.add_middleware(
685
+ CORSMiddleware,
686
+ allow_origins=["*"],
687
+ allow_credentials=True,
688
+ allow_methods=["*"],
689
+ allow_headers=["*"],
690
+ )
691
 
692
  SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
693
  openai_api_key = os.getenv("OPENAI_API_KEY")
 
944
  - Explains why this opportunity aligns with their background
945
  - Suggests next steps (like a call or meeting)
946
  - Maintains a warm, professional tone
947
+ - if in the Evaluation the verdict is rejected, Send a rejection message instead.
948
  """
949
  else: # followup
950
  prompt += """
 
1040
  # ------------------------------------------
1041
  @app.get("/")
1042
  async def root():
1043
+ """Root endpoint - public access"""
1044
  return {
1045
  "message": "Recruitment Message Generator API",
1046
  "version": "1.0.0",
1047
+ "authentication": "Required for all endpoints except / and /health",
1048
+ "auth_header": "X-API-Key",
1049
  "endpoints": [
1050
  "/generate-message",
1051
  "/refine-message",
1052
  "/authenticate",
1053
  "/send-message",
1054
  "/docs"
 
1055
  ]
1056
  }
1057
 
1058
+ @app.post("/send-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
1059
  async def send_message(request: SendMessageRequest):
1060
  try:
1061
  # Authenticate sender
 
1104
  error=str(e)
1105
  )
1106
 
1107
+ @app.post("/generate-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
1108
  async def generate_message(request: GenerateMessageRequest, background_tasks: BackgroundTasks):
1109
  try:
1110
  current_position = f"{request.current_role} at {request.current_company}"
 
1178
  error=str(e)
1179
  )
1180
 
1181
+ @app.post("/refine-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
1182
  async def refine_message(request: FeedbackRequest):
1183
  try:
1184
  email_subject, refined_message = refine_message_with_feedback(
 
1198
  error=str(e)
1199
  )
1200
 
1201
+ @app.post("/authenticate", response_model=AuthenticateResponse, dependencies=[Depends(verify_api_key)])
1202
  async def authenticate_user(request: AuthenticateRequest):
1203
  try:
1204
  if check_user_token_exists(request.email):
 
1250
 
1251
  @app.get("/health")
1252
  async def health_check():
1253
+ """Health check endpoint - public access"""
1254
  return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
1255
 
1256
+ # Protected documentation endpoint
1257
+ @app.get("/docs", dependencies=[Depends(verify_api_key)])
1258
+ async def get_documentation():
1259
+ """This will be handled by FastAPI's built-in docs"""
1260
+ pass
1261
+
1262
  if __name__ == "__main__":
1263
  import uvicorn
1264
+ uvicorn.run(app, host="0.0.0.0", port=8000)