Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion chalicelib/api/broadcast.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from chalice import Blueprint
from chalicelib.modules.ses import ses, SesDestination
from chalicelib.modules.mongo import MongoModule
from chalicelib.decorators import auth
from chalicelib.services.BroadcastService import broadcast_service
from chalicelib.models.roles import Roles
import boto3

broadcast_api = Blueprint(__name__)
Expand All @@ -18,6 +20,7 @@ def test_functions():

# why is GET needed?
@broadcast_api.route("/broadcast", methods=["GET", "POST"], cors=True)
@auth(broadcast_api, roles=[Roles.ADMIN])
def send_announcement():
MongoServer = MongoModule()
MongoServer.connect()
Expand Down Expand Up @@ -49,7 +52,7 @@ def test_email():

for email in emails:
broadcast_service.send_newsletter(
subject="Test Email",
subject="PCT Weekly Newsletter Beta Test",
content=html["html"],
recipients=[email],
)
Expand Down
94 changes: 94 additions & 0 deletions chalicelib/modules/google_sheets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import boto3
import json
from thefuzz import fuzz
import re


class GoogleSheetsModule:
Expand Down Expand Up @@ -133,6 +134,99 @@ def find_next_available_col(self, spreadsheet_id: str, sheet_name: str):

return next_col

def get_sheet_with_grid_data(self, spreadsheet_id: str, sheet_name: str):
"""
Retrieves full grid data including cell metadata with hyperlink information.

Grid data is a detailed representation of sheet contents returned by the Google Sheets API
when includeGridData=True. It contains comprehensive information about each cell including
values, formatting, data validation rules, hyperlinks, notes, and other metadata. This
structure allows access to all cell information beyond just the displayed values.

Args:
spreadsheet_id (str): The ID of the spreadsheet.
sheet_name (str): The name of the sheet to retrieve.

Returns:
dict: The complete sheet data including grid data with hyperlinks.
"""
# Get the sheet ID first
sheets = self.get_sheets(spreadsheet_id, include_properties=True)
sheet_id = next((sheet.get("sheetId") for sheet in sheets if sheet.get("title") == sheet_name), None)

if sheet_id is None:
return None

# Request the sheet with full grid data including hyperlinks
response = self.service.spreadsheets().get(
spreadsheetId=spreadsheet_id,
ranges=[sheet_name],
includeGridData=True
).execute()

if "sheets" in response and len(response["sheets"]) > 0:
return response["sheets"][0]

return None

def get_hyperlink_from_grid_data(self, sheet_data, row_index, col_index):
"""
Extracts hyperlink from grid data for a specific cell.

Args:
sheet_data (dict): Sheet data returned by get_sheet_with_grid_data.
row_index (int): The row index (1-based).
col_index (int): The column index (0-based).

Returns:
str: The hyperlink URL or None if not found.
"""
if sheet_data is None:
return None
if "data" not in sheet_data:
return None
if sheet_data["data"] is None:
return None

grid_data = sheet_data["data"][0]

# Convert to 0-based row index
row_index = row_index - 1

# Check if the row exists
if "rowData" not in grid_data or row_index >= len(grid_data["rowData"]):
return None

row_data = grid_data["rowData"][row_index]

# Check if the cell exists in this row
if "values" not in row_data or col_index >= len(row_data["values"]):
return None

cell = row_data["values"][col_index]

# Check if the cell has a hyperlink
if "hyperlink" in cell.get("userEnteredValue", {}).get("formulaValue", ""):
# Extract URL from formula
formula = cell["userEnteredValue"]["formulaValue"]
match = re.match(r'=HYPERLINK\("(.*?)"', formula)
if match:
return match.group(1)
elif "hyperlink" in cell.get("hyperlink", {}):
return cell["hyperlink"]
elif "hyperlinkUrl" in cell.get("dataValidation", {}):
return cell["dataValidation"]["hyperlinkUrl"]
elif "hyperlinkDisplayType" in cell and "hyperlinkUrl" in cell:
return cell["hyperlinkUrl"]

# For UI-inserted hyperlinks
if "userEnteredFormat" in cell and "textFormat" in cell["userEnteredFormat"]:
text_format = cell["userEnteredFormat"]["textFormat"]
if "link" in text_format and "uri" in text_format["link"]:
return text_format["link"]["uri"]

return None

def add_event(self, spreadsheet_id: str, sheet_tab: str, event_name: str, col: str):
self.service.spreadsheets().values().append(
spreadsheetId=spreadsheet_id,
Expand Down
185 changes: 142 additions & 43 deletions chalicelib/services/BroadcastService.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
from chalicelib.modules.ses import ses, SesDestination
from chalicelib.services.JobPostingService import job_posting_service
from chalicelib.utils import get_newsletter_css
from typing import List, Dict

# Job sources constant for better maintainability
JOB_SOURCE_LIST = {
"Tech": [
{
"name": "SimplifyJobs",
"link": "https://github.com/SimplifyJobs/Summer2025-Internships/blob/dev/README.md",
"display_link": "https://github.com/SimplifyJobs/Summer2025-Internships"
},
{
"name": "Cvrve",
"link": "https://github.com/cvrve/Summer2025-Internships/blob/dev/README.md",
"display_link": "https://github.com/cvrve/Summer2025-Internships"
}
],
"Finance": [
{
"name": "RecruitU",
"link": "https://docs.google.com/spreadsheets/d/15za1luZR08YmmBIFOAk6-GJB3T22StEuiZgFFuJeKW0/",
"display_link": "https://docs.google.com/spreadsheets/d/15za1luZR08YmmBIFOAk6-GJB3T22StEuiZgFFuJeKW0/"}
],
"Consulting": [
{
"name": "Jobright-ai",
"link": "https://github.com/jobright-ai/2025-Consultant-Internship/blob/master/README.md",
"display_link": "https://github.com/jobright-ai/2025-Consultant-Internship"
}
],
"Marketing": [
{
"name": "Jobright-ai",
"link": "https://github.com/jobright-ai/2025-Marketing-Internship/blob/master/README.md",
"display_link": "https://github.com/jobright-ai/2025-Marketing-Internship"
}
]
}

class BroadcastService:
def __init__(self):
self.ps = job_posting_service
Expand All @@ -10,23 +47,44 @@ def generate_section_html(self, section_title: str, jobs: List[Dict]) -> str:
"""
Generates an HTML section for a given list of jobs.
"""
html = f"<div class='jobs-section'><h2>{section_title} Opportunities</h2><ul class='job-list'>"
# Banner image URLs based on section title
banner_urls = {
"Finance": "https://whyphi-public.s3.us-east-1.amazonaws.com/finance_opportunities.jpg",
"Tech": "https://whyphi-public.s3.us-east-1.amazonaws.com/tech_opportunities.jpg",
"Consulting": "https://whyphi-public.s3.us-east-1.amazonaws.com/consulting_opportunities.jpg",
"Marketing": "https://whyphi-public.s3.us-east-1.amazonaws.com/marketing_opportunities.jpg"
}

banner_url = banner_urls.get(section_title, "")
banner_html = f'<div class="section-banner"><img src="{banner_url}" alt="{section_title} Banner" /></div>' if banner_url else ""

html = f"""
<div class='jobs-section'>
{banner_html}
"""

for job in jobs:
company = job.get("company", "N/A")
role = job.get("role", "N/A")
link = job.get("link", "#")
deadline = job.get("date", "N/A")

job_line = (
f"{company} | {role} | "
f"<a href='{link}' target='_blank'>Link</a> | {deadline}"
)
# Use "Deadline" for Finance jobs and "Posted" for all other job types
date_label = "Deadline" if section_title == "Finance" else "Posted"

html += f"<li class='job-item'>{job_line}</li>"
html += "</ul></div>"
html += f"""
<div class='job-item'>
<div class='job-title'>{company} | {role}</div>
<div class='job-details'>
<a href='{link}' target='_blank'>Apply</a> | {date_label}: {deadline}
</div>
</div>
"""

html += "</div>"
return html

def generate_newsletter_content(self, custom_content: str = "") -> Dict:
def generate_newsletter_content(self, custom_content: str = "Thanks for signing up for the PCT Weekly Newsletter beta test! We would love to incorporate your feedback as much as possible so if you do, please fill out this form: https://forms.gle/nW8cJPzXvHnyfkzV9 or reach out to Matthew and Vincent at [email protected] and [email protected] or through slack.\nBest of luck with finals this week!") -> Dict:
"""
Generates a complete newsletter with four sections:
- Finance: Data from Google Sheets
Expand All @@ -37,55 +95,96 @@ def generate_newsletter_content(self, custom_content: str = "") -> Dict:
Returns:
Dict: A dictionary with HTML content and the raw job data.
"""
# Finance jobs from Google Sheets
finance_jobs = self.ps.getFinanceJobs()
# Finance jobs from RecruitU Google Sheets
finance_jobs = self.ps.get_finance_jobs()

# Tech jobs: Merge data from both Simplify and Crve GitHub repos
tech_jobs_simplify = self.ps.getJobs("https://github.com/SimplifyJobs/Summer2025-Internships/blob/dev/README.md")
tech_jobs_cvre = self.ps.getJobs("https://github.com/cvrve/Summer2025-Internships/blob/dev/README.md")
tech_jobs = tech_jobs_simplify + tech_jobs_cvre
# Fetch jobs from sources defined in JOB_SOURCE_LIST
tech_jobs = []
for source in JOB_SOURCE_LIST["Tech"]:
tech_jobs += self.ps.get_jobs(source["link"])

# Consulting jobs from jobright-ai Consultant GitHub repo
consulting_jobs = self.ps.getJobs("https://github.com/jobright-ai/2025-Consultant-Internship/blob/master/README.md")
consulting_jobs = []
for source in JOB_SOURCE_LIST["Consulting"]:
consulting_jobs += self.ps.get_jobs(source["link"])

# Marketing jobs from jobright-ai Marketing GitHub repo
marketing_jobs = self.ps.getJobs("https://github.com/jobright-ai/2025-Marketing-Internship/blob/master/README.md")
marketing_jobs = []
for source in JOB_SOURCE_LIST["Marketing"]:
marketing_jobs += self.ps.get_jobs(source["link"])

# Generate HTML for each section
finance_section = self.generate_section_html("Finance", finance_jobs)
tech_section = self.generate_section_html("Tech", tech_jobs)
consulting_section = self.generate_section_html("Consulting", consulting_jobs)
marketing_section = self.generate_section_html("Marketing", marketing_jobs)

# Common CSS for styling the newsletter sections
css = """
<style>
.jobs-section { margin: 20px 0; }
.job-list { list-style-type: none; padding-left: 0; }
.job-item {
margin-bottom: 15px;
padding: 10px;
border-left: 3px solid #007bff;
background-color: #f8f9fa;
}
.job-header { margin-bottom: 5px; }
.company-name { color: #007bff; text-decoration: none; }
.deadline { color: #6c757d; margin-left: 10px; }
.job-role { margin: 5px 0; color: #212529; }
</style>
# Get CSS from utils function
css = get_newsletter_css()

# Generate job sources HTML dynamically from JOB_SOURCE_LIST
job_sources_html = """
<div class="job-sources">
<p>All jobs listed have been posted within the last 7 days from these repositories:</p>
"""

for category, sources in JOB_SOURCE_LIST.items():
job_sources_html += f"<p>{category}: "
links = []
for i, source in enumerate(sources):
links.append(f'<a href="{source["display_link"]}" target="_blank">{source["name"]}</a>')

job_sources_html += " and ".join(links) + "</p>"

job_sources_html += """
<p>These repositories are updated daily, so please check them often. Best of luck with your job search!</p>
</div>
"""

full_html = f"""
{css}
<div class="newsletter">
<div class="custom-content">
{custom_content}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{css}
</head>
<body>
<div class="newsletter-container">
<div class="newsletter-header">
</div>

<div class="welcome-message">
<h2>Welcome to PCT's Weekly Newsletter</h2>
{custom_content}
</div>

<div class="main-content">
<div class="job-opportunities-banner">
<img src="https://whyphi-public.s3.us-east-1.amazonaws.com/job_opportunities.jpg" alt="Job Opportunities" />
</div>

{job_sources_html}

{tech_section}
{finance_section}
{marketing_section}
{consulting_section}

<div class="events-section">
<h2 class="section-title">Events</h2>
<!-- Events content can be added here -->
</div>
</div>

<div class="footer">
<div class="social-links">
<a href="#" target="_blank">🔗</a>
<a href="#" target="_blank">📷</a>
<a href="#" target="_blank">📱</a>
</div>
</div>
</div>
{finance_section}
{tech_section}
{consulting_section}
{marketing_section}
</div>
</body>
</html>
"""

return {
Expand Down
Loading