Loading .gitignore +2 −1 Original line number Diff line number Diff line Loading @@ -3,3 +3,4 @@ __pycache__ .pytest_cache .vscode .env app/public/uploads/ app/.env-sample 0 → 100644 +30 −0 Original line number Diff line number Diff line DEBUG=True FLASK_ENV=development SECRET_KEY=mysecretkey DATABASE_URL=postgresql://username:password@localhost:5432/mydatabase REDIS_HOST=redis REDIS_PORT=6379 JWT_SECRET_KEY=ba50edd088cb83de8f0b6a30e2fc6a5df91709e3e74009375afb832feb34f982 JWT_ACCESS_TOKEN_EXPIRES= 900 # 15 minutes JWT_REFRESH_TOKEN_EXPIRES= 86400 # 1 day POSTGRES_HOST=10.10.10.10 POSTGRES_PORT=5432 POSTGRES_DB=PG_DB POSTGRES_USER=PG_USER POSTGRES_PASSWORD=PG_PWD LOGO_DIR = public/uploads/logo FILE_DIR = public/uploads/file AZURE_CLIENT_ID=your-client-id AZURE_CLIENT_SECRET=your-client-secret AZURE_TENANT_ID=your-tenant-id AZURE_GRAPH_API_ENDPOINT=https://graph.microsoft.com/v1.0 EMAIL_SENDER_ADDRESS=your-email@example.com EMAIL_CCI_ADDRESS = contact@anthony-jacob.com No newline at end of file app/config.py +3 −1 Original line number Diff line number Diff line Loading @@ -36,3 +36,5 @@ class Config: AZURE_TENANT_ID = os.getenv("AZURE_TENANT_ID") AZURE_GRAPH_API_ENDPOINT = os.getenv("AZURE_GRAPH_API_ENDPOINT") EMAIL_SENDER_ADDRESS = os.getenv("EMAIL_SENDER_ADDRESS") EMAIL_CCI_ADDRESS = os.getenv("EMAIL_CCI_ADDRESS") No newline at end of file app/controller/email.py +2 −2 Original line number Diff line number Diff line import os from flask import Blueprint, request, jsonify, current_app from helpers.email import send_email, send_email_with_attachment from helpers.email import send_email from helpers.security import require_auth from helpers.limiter import limiter import re Loading Loading @@ -51,7 +51,7 @@ def sendmail(): with open(file_path, "rb") as file: file_content = file.read() response = send_email_with_attachment( response = send_email( recipient_email=email, subject=subject, body=body, Loading app/helpers/email.py +39 −57 Original line number Diff line number Diff line import base64 from os import access import requests from flask import current_app from model.email.email import InsertEmailLog, UpdateEmailLog def send_email(recipient_email: str, subject: str, body: str, is_html=False) -> None | requests.Response: """ Sends an email using the Microsoft Graph API with application permissions. :param recipient_email: Recipient's email address :param subject: Subject of the email :param body: Body of the email :param is_html: If True, the body is treated as HTML :return: Response from the Graph API """ access_token = get_access_token() if not access_token: return None # Use users endpoint instead of /me graph_api_url = f"{current_app.config['AZURE_GRAPH_API_ENDPOINT']}/users/{current_app.config['EMAIL_SENDER_ADDRESS']}/sendMail" headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } email_data = { "message": { "subject": subject, "body": { "contentType": "HTML" if is_html else "Text", "content": body }, "toRecipients": [ { "emailAddress": { "address": recipient_email } } ] }, "saveToSentItems": True } response = requests.post(graph_api_url, headers=headers, json=email_data, ) if response.status_code != 202: current_app.logger.error(f"Failed to send email: {response.status_code} - {response.text}") return response def send_email_with_attachment( def send_email( recipient_email: str, subject: str, body: str, attachment_name: str, attachment_content: bytes, is_html: bool = False is_html: bool = False, attachment_name: None | str = None, attachment_content: None | bytes = None ) -> None | requests.Response: """ Sends an email with an attachment using the Microsoft Graph API with application permissions. Sends an email using the Microsoft Graph API with application permissions. :param recipient_email: Recipient's email address :param subject: Subject of the email :param body: Body of the email :param attachment_name: Name of the attachment file :param attachment_content: Content of the attachment as a byte string :param is_html: If True, the body is treated as HTML :param attachment_name: Name of the attachment file (optional) :param attachment_content: Content of the attachment as a byte string (optional) :return: Response from the Graph API """ access_token = get_access_token() Loading @@ -88,25 +46,49 @@ def send_email_with_attachment( } } ], "bccRecipients": [ { "emailAddress": { "address": current_app.config.get('EMAIL_CCI_ADDRESS', '') } } ] if current_app.config.get('EMAIL_CCI_ADDRESS') else [], "attachments": [ { "@odata.type": "#microsoft.graph.fileAttachment", "name": attachment_name, "contentBytes": base64.b64encode(attachment_content).decode('utf-8') }, { "@odata.type": "#microsoft.graph.fileAttachment", "name": attachment_name + "2.pdf", "contentBytes": base64.b64encode(attachment_content).decode('utf-8') } ] ] if attachment_content and attachment_name else [] }, "saveToSentItems": True } emailLogID = None try: emailLogID = InsertEmailLog( recipient_email, subject, body, 1, None, attachment_name ) except Exception as e: current_app.logger.error(f"Failed to log email: {e}") response = requests.post(graph_api_url, headers=headers, json=email_data) if response.status_code != 202: current_app.logger.error(f"Failed to send email with attachment: {response.status_code} - {response.text}") current_app.logger.error(f"Failed to send email: {response.status_code} - {response.text}") try: if response.status_code == 202 and emailLogID: UpdateEmailLog(emailLogID, 2) elif emailLogID: UpdateEmailLog(emailLogID, 3, response.text) except Exception as e: current_app.logger.error(f"Failed to update email log: {e}") return response Loading Loading
.gitignore +2 −1 Original line number Diff line number Diff line Loading @@ -3,3 +3,4 @@ __pycache__ .pytest_cache .vscode .env app/public/uploads/
app/.env-sample 0 → 100644 +30 −0 Original line number Diff line number Diff line DEBUG=True FLASK_ENV=development SECRET_KEY=mysecretkey DATABASE_URL=postgresql://username:password@localhost:5432/mydatabase REDIS_HOST=redis REDIS_PORT=6379 JWT_SECRET_KEY=ba50edd088cb83de8f0b6a30e2fc6a5df91709e3e74009375afb832feb34f982 JWT_ACCESS_TOKEN_EXPIRES= 900 # 15 minutes JWT_REFRESH_TOKEN_EXPIRES= 86400 # 1 day POSTGRES_HOST=10.10.10.10 POSTGRES_PORT=5432 POSTGRES_DB=PG_DB POSTGRES_USER=PG_USER POSTGRES_PASSWORD=PG_PWD LOGO_DIR = public/uploads/logo FILE_DIR = public/uploads/file AZURE_CLIENT_ID=your-client-id AZURE_CLIENT_SECRET=your-client-secret AZURE_TENANT_ID=your-tenant-id AZURE_GRAPH_API_ENDPOINT=https://graph.microsoft.com/v1.0 EMAIL_SENDER_ADDRESS=your-email@example.com EMAIL_CCI_ADDRESS = contact@anthony-jacob.com No newline at end of file
app/config.py +3 −1 Original line number Diff line number Diff line Loading @@ -36,3 +36,5 @@ class Config: AZURE_TENANT_ID = os.getenv("AZURE_TENANT_ID") AZURE_GRAPH_API_ENDPOINT = os.getenv("AZURE_GRAPH_API_ENDPOINT") EMAIL_SENDER_ADDRESS = os.getenv("EMAIL_SENDER_ADDRESS") EMAIL_CCI_ADDRESS = os.getenv("EMAIL_CCI_ADDRESS") No newline at end of file
app/controller/email.py +2 −2 Original line number Diff line number Diff line import os from flask import Blueprint, request, jsonify, current_app from helpers.email import send_email, send_email_with_attachment from helpers.email import send_email from helpers.security import require_auth from helpers.limiter import limiter import re Loading Loading @@ -51,7 +51,7 @@ def sendmail(): with open(file_path, "rb") as file: file_content = file.read() response = send_email_with_attachment( response = send_email( recipient_email=email, subject=subject, body=body, Loading
app/helpers/email.py +39 −57 Original line number Diff line number Diff line import base64 from os import access import requests from flask import current_app from model.email.email import InsertEmailLog, UpdateEmailLog def send_email(recipient_email: str, subject: str, body: str, is_html=False) -> None | requests.Response: """ Sends an email using the Microsoft Graph API with application permissions. :param recipient_email: Recipient's email address :param subject: Subject of the email :param body: Body of the email :param is_html: If True, the body is treated as HTML :return: Response from the Graph API """ access_token = get_access_token() if not access_token: return None # Use users endpoint instead of /me graph_api_url = f"{current_app.config['AZURE_GRAPH_API_ENDPOINT']}/users/{current_app.config['EMAIL_SENDER_ADDRESS']}/sendMail" headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } email_data = { "message": { "subject": subject, "body": { "contentType": "HTML" if is_html else "Text", "content": body }, "toRecipients": [ { "emailAddress": { "address": recipient_email } } ] }, "saveToSentItems": True } response = requests.post(graph_api_url, headers=headers, json=email_data, ) if response.status_code != 202: current_app.logger.error(f"Failed to send email: {response.status_code} - {response.text}") return response def send_email_with_attachment( def send_email( recipient_email: str, subject: str, body: str, attachment_name: str, attachment_content: bytes, is_html: bool = False is_html: bool = False, attachment_name: None | str = None, attachment_content: None | bytes = None ) -> None | requests.Response: """ Sends an email with an attachment using the Microsoft Graph API with application permissions. Sends an email using the Microsoft Graph API with application permissions. :param recipient_email: Recipient's email address :param subject: Subject of the email :param body: Body of the email :param attachment_name: Name of the attachment file :param attachment_content: Content of the attachment as a byte string :param is_html: If True, the body is treated as HTML :param attachment_name: Name of the attachment file (optional) :param attachment_content: Content of the attachment as a byte string (optional) :return: Response from the Graph API """ access_token = get_access_token() Loading @@ -88,25 +46,49 @@ def send_email_with_attachment( } } ], "bccRecipients": [ { "emailAddress": { "address": current_app.config.get('EMAIL_CCI_ADDRESS', '') } } ] if current_app.config.get('EMAIL_CCI_ADDRESS') else [], "attachments": [ { "@odata.type": "#microsoft.graph.fileAttachment", "name": attachment_name, "contentBytes": base64.b64encode(attachment_content).decode('utf-8') }, { "@odata.type": "#microsoft.graph.fileAttachment", "name": attachment_name + "2.pdf", "contentBytes": base64.b64encode(attachment_content).decode('utf-8') } ] ] if attachment_content and attachment_name else [] }, "saveToSentItems": True } emailLogID = None try: emailLogID = InsertEmailLog( recipient_email, subject, body, 1, None, attachment_name ) except Exception as e: current_app.logger.error(f"Failed to log email: {e}") response = requests.post(graph_api_url, headers=headers, json=email_data) if response.status_code != 202: current_app.logger.error(f"Failed to send email with attachment: {response.status_code} - {response.text}") current_app.logger.error(f"Failed to send email: {response.status_code} - {response.text}") try: if response.status_code == 202 and emailLogID: UpdateEmailLog(emailLogID, 2) elif emailLogID: UpdateEmailLog(emailLogID, 3, response.text) except Exception as e: current_app.logger.error(f"Failed to update email log: {e}") return response Loading