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

@@ -1,4 +1,22 @@
/* javascript form handling */
document.addEventListener("DOMContentLoaded", function () {
const newNkInput = document.getElementById("new-negative-keyword");
if (newNkInput) {
newNkInput.addEventListener("input", function () {
const val = this.value.trim();
const existing = Array.from(
document.querySelectorAll('input[name="negative_keyword"]')
).map((el) => el.value);
if (existing.includes(val)) {
this.setCustomValidity("Keyword already exists");
this.reportValidity();
} else {
this.setCustomValidity("");
}
});
}
});
document
.getElementById("user-settings-form")
.addEventListener("submit", function (event) {
@@ -10,11 +28,15 @@ document
// Collect selected regions and keywords
const selectedRegions = [];
const selectedKeywords = [];
const selectedNegativeKeywords = [];
formData.forEach((value, key) => {
if (key === "region") {
selectedRegions.push(value);
} else if (key === "keyword") {
selectedKeywords.push(value);
} else if (key === "negative_keyword") {
selectedNegativeKeywords.push(value);
}
});
@@ -30,10 +52,21 @@ document
selectedKeywords.push(newKeyword);
}
// Add new negative keyword if provided
const newNegativeKeyword = formData.get("new-negative-keyword").trim();
if (newNegativeKeyword) {
if (selectedNegativeKeywords.includes(newNegativeKeyword)) {
alert("Negative keyword already exists!");
return;
}
selectedNegativeKeywords.push(newNegativeKeyword);
}
// Prepare data to send
const dataToSend = {
regions: selectedRegions,
keywords: selectedKeywords,
negative_keywords: selectedNegativeKeywords,
csrf_token: formData.get("csrf_token"),
};