feat: Implement email sending utilities and templates for job notifications
Some checks failed
CI/CD Pipeline / test (push) Failing after 4m9s

- Added email_service.py for sending emails with SMTP configuration.
- Introduced email_templates.py to render job alert email subjects and bodies.
- Enhanced scraper.py to extract contact information from job listings.
- Updated settings.js to handle negative keyword input validation.
- Created email.html and email_templates.html for managing email subscriptions and templates in the admin interface.
- Modified base.html to include links for email alerts and templates.
- Expanded user settings.html to allow management of negative keywords.
- Updated utils.py to include functions for retrieving negative keywords and email settings.
- Enhanced job filtering logic to exclude jobs containing negative keywords.
This commit is contained in:
2025-11-28 18:15:08 +01:00
parent 8afb208985
commit 2185a07ff0
23 changed files with 2660 additions and 63 deletions

View File

@@ -0,0 +1,148 @@
import pytest
from web.db import (
db_init,
create_or_update_user,
upsert_negative_keyword,
set_user_negative_keywords,
get_user_negative_keywords,
upsert_listing,
upsert_job_details,
get_all_jobs,
UserNegativeKeyword,
NegativeKeyword
)
from web.app import app
from web.utils import filter_jobs
@pytest.fixture
def client():
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
with app.test_client() as client:
with app.app_context():
db_init()
yield client
def test_negative_keyword_db_ops():
db_init()
username = "test_neg_user"
create_or_update_user(username, "password")
# Test upsert
kid = upsert_negative_keyword("scam")
assert kid > 0
kid2 = upsert_negative_keyword("scam")
assert kid == kid2
# Test set/get
set_user_negative_keywords(username, ["scam", "unpaid"])
nks = get_user_negative_keywords(username)
assert len(nks) == 2
assert "scam" in nks
assert "unpaid" in nks
# Test update
set_user_negative_keywords(username, ["scam"])
nks = get_user_negative_keywords(username)
assert len(nks) == 1
assert "scam" in nks
assert "unpaid" not in nks
# Test clear
set_user_negative_keywords(username, [])
nks = get_user_negative_keywords(username)
assert len(nks) == 0
def test_settings_endpoint(client):
username = "test_settings_user"
create_or_update_user(username, "password")
# Login
client.post('/login', data={'username': username, 'password': 'password'})
# Post settings
resp = client.post('/settings', json={
'regions': [],
'keywords': [],
'negative_keywords': ['spam', 'junk']
})
assert resp.status_code == 200
# Verify DB
nks = get_user_negative_keywords(username)
assert "spam" in nks
assert "junk" in nks
def test_job_filtering_with_negative_keywords():
# Setup jobs
jobs = [
{"title": "Great Job", "description": "Good pay"},
{"title": "Bad Job", "description": "This is a scam"},
{"title": "Okay Job", "description": "Average pay"},
]
# Filter
filtered = filter_jobs(jobs, negative_keywords=["scam"])
assert len(filtered) == 2
assert "Bad Job" not in [j['title'] for j in filtered]
filtered = filter_jobs(jobs, negative_keywords=["pay"])
assert len(filtered) == 1
assert "Bad Job" in [j['title']
for j in filtered] # "scam" job doesn't have "pay"
def test_jobs_endpoint_filtering(client):
username = "test_filter_user"
create_or_update_user(username, "password")
# Setup DB with jobs
upsert_listing(
url="http://example.com/1",
region="sfbay",
keyword="python",
title="Good Python Job",
pay="$100k",
location="SF",
timestamp="now"
)
upsert_job_details({
"url": "http://example.com/1",
"id": "1",
"title": "Good Python Job",
"description": "This is a legit job."
})
upsert_listing(
url="http://example.com/2",
region="sfbay",
keyword="python",
title="Bad Python Job",
pay="$100k",
location="SF",
timestamp="now"
)
upsert_job_details({
"url": "http://example.com/2",
"id": "2",
"title": "Bad Python Job",
"description": "This is a scam job."
})
# Login
client.post('/login', data={'username': username, 'password': 'password'})
# Set negative keywords
set_user_negative_keywords(username, ["scam"])
# Fetch jobs
resp = client.get('/jobs')
data = resp.get_json()
titles = [j['title'] for j in data]
assert "Good Python Job" in titles
assert "Bad Python Job" not in titles