ak0601 commited on
Commit
2365ef1
·
verified ·
1 Parent(s): beefcb9

Update app_hug.py

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