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,102 @@
{% extends 'base.html' %}
{% block content %}
<h2>Email Templates</h2>
<section>
<h3>Available Templates</h3>
{% if not templates %}
<p>No templates found. Create one below to get started.</p>
{% else %}
<table>
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th>Status</th>
<th>Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for template in templates %}
<tr>
<td>{{ template.name }}</td>
<td>{{ template.slug }}</td>
<td>{{ 'Active' if template.is_active else 'Inactive' }}</td>
<td>{{ template.updated_at or template.created_at or '' }}</td>
<td style="display: flex; gap: 0.5rem;">
<a class="button" href="{{ url_for('admin_email_templates', template_id=template.template_id) }}">Edit</a>
<a class="button" href="{{ url_for('admin_email_templates', preview_id=template.template_id) }}">Preview</a>
<form method="post" onsubmit="return confirm('Delete template {{ template.name }}?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="action" value="delete" />
<input type="hidden" name="template_id" value="{{ template.template_id }}" />
<button type="submit">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</section>
<section>
<h3>{{ 'Edit Template' if editing else 'Create Template' }}</h3>
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="action" value="{{ 'update' if editing else 'create' }}" />
{% if editing %}
<input type="hidden" name="template_id" value="{{ editing.template_id }}" />
{% endif %}
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{ editing.name if editing else '' }}" required />
</div>
<div>
<label for="slug">Slug</label>
<input type="text" id="slug" name="slug" placeholder="job-alert" value="{{ editing.slug if editing else '' }}" />
<small>Leave blank to reuse the name. Slug must be URL friendly (letters, numbers, dashes).</small>
</div>
<div>
<label for="subject">Subject Template</label>
<input type="text" id="subject" name="subject" value="{{ editing.subject if editing else '' }}" required />
</div>
<div>
<label for="body">Body Template</label>
<textarea id="body" name="body" rows="12" required>{{ editing.body if editing else '' }}</textarea>
</div>
<div>
<label>
<input type="checkbox" name="is_active" {% if editing is none or editing.is_active %}checked{% endif %} />
Active
</label>
</div>
<button type="submit">{{ 'Update Template' if editing else 'Create Template' }}</button>
{% if editing %}
<a class="button" href="{{ url_for('admin_email_templates') }}">Cancel</a>
{% endif %}
</form>
<aside>
<h4>Available placeholders</h4>
<ul>
<li><code>{count}</code> number of jobs in the alert</li>
<li><code>{count_label}</code> "No new jobs" or "X new jobs"</li>
<li><code>{scope}</code> formatted region/keyword context</li>
<li><code>{region}</code>, <code>{keyword}</code></li>
<li><code>{timestamp}</code> formatted timestamp</li>
<li><code>{jobs_section}</code> newline-prefixed block of job entries</li>
<li><code>{jobs_message}</code> jobs block without leading newline</li>
</ul>
</aside>
</section>
{% if preview %}
<section>
<h3>Preview: {{ preview_template.name if preview_template else 'Job Alert' }}</h3>
<article>
<h4>Subject</h4>
<pre>{{ preview.subject }}</pre>
<h4>Body</h4>
<pre>{{ preview.body }}</pre>
</article>
</section>
{% endif %}
{% endblock %}