From 360e04d6774dbf8e3feb33cd03003bd97bc39d68 Mon Sep 17 00:00:00 2001
From: Thomas Hackner
Date: Sat, 4 Jul 2026 17:10:13 +0200
Subject: [PATCH] Confirm before sending; add address + contact to job offers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
"Bewerbung senden" now asks for an extra confirmation (with the
recipient) before dispatching.
Job offers gain adresse and ansprechpartner fields — shown, editable,
and carried through the REST API upsert (Swagger updated). Taking an
offer over now writes the employer address (street, number, city) and
the contact person into the application's AI notes (llm_notizen), so the
LLM can use them for the letter's Anschriftfeld and salutation.
Co-Authored-By: Claude Opus 4.8
---
lib/api.js | 8 +++++---
lib/openapi.js | 4 ++++
server.js | 25 ++++++++++++++++++++++---
views/bewerbung.ejs | 3 ++-
views/jobangebote.ejs | 20 ++++++++++++++++++++
views/jobangebote_uebernommen.ejs | 12 ++++++++++++
6 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/lib/api.js b/lib/api.js
index 1a619b2..6525b4b 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -614,6 +614,8 @@ function createExternalApi(deps) {
sanitizeInput(b.firma),
sanitizeInput(b.stelle),
sanitizeInput(b.ort || ''),
+ sanitizeInput(b.adresse || ''),
+ sanitizeInput(b.ansprechpartner || ''),
sanitizeInput(b.gehalt || ''),
sanitizeInput(b.beschreibung || ''),
sanitizeInput(b.quelle_url || ''),
@@ -642,7 +644,7 @@ function createExternalApi(deps) {
if (existing) {
await dbRun(
- `UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, gehalt = ?,
+ `UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, adresse = ?, ansprechpartner = ?, gehalt = ?,
beschreibung = ?, quelle_url = ?, art = ?, anzeige_datum = ?, kontakt_email = ?, status = ?,
url_norm = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
[...fields, existing.id]
@@ -653,8 +655,8 @@ function createExternalApi(deps) {
const result = await dbRun(
`INSERT INTO jobangebote
- (external_id, quelle, firma, stelle, ort, gehalt, beschreibung, quelle_url, art, anzeige_datum, kontakt_email, status, url_norm)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ (external_id, quelle, firma, stelle, ort, adresse, ansprechpartner, gehalt, beschreibung, quelle_url, art, anzeige_datum, kontakt_email, status, url_norm)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[externalId, quelle, ...fields]
);
const row = await dbGet('SELECT * FROM jobangebote WHERE id = ?', [result.lastID]);
diff --git a/lib/openapi.js b/lib/openapi.js
index 1992e2c..c638020 100644
--- a/lib/openapi.js
+++ b/lib/openapi.js
@@ -855,6 +855,8 @@ function buildOpenApiSpec(baseUrl = '') {
firma: { type: 'string' },
stelle: { type: 'string' },
ort: { type: 'string', nullable: true },
+ adresse: { type: 'string', nullable: true, description: 'Anschrift des Arbeitgebers (Straße, Hausnummer, PLZ, Ort)' },
+ ansprechpartner: { type: 'string', nullable: true, description: 'Name des Ansprechpartners für die Bewerbung' },
gehalt: { type: 'string', nullable: true },
beschreibung: { type: 'string', nullable: true },
quelle_url: { type: 'string', nullable: true },
@@ -877,6 +879,8 @@ function buildOpenApiSpec(baseUrl = '') {
firma: { type: 'string' },
stelle: { type: 'string' },
ort: { type: 'string' },
+ adresse: { type: 'string', description: 'Anschrift des Arbeitgebers (Straße, Hausnummer, PLZ, Ort)' },
+ ansprechpartner: { type: 'string', description: 'Name des Ansprechpartners für die Bewerbung' },
gehalt: { type: 'string' },
beschreibung: { type: 'string', description: 'Stellenbeschreibungstext' },
quelle_url: { type: 'string' },
diff --git a/server.js b/server.js
index 1bafe84..f847df4 100644
--- a/server.js
+++ b/server.js
@@ -640,6 +640,8 @@ function initializeDatabase() {
firma TEXT NOT NULL,
stelle TEXT NOT NULL,
ort TEXT,
+ adresse TEXT,
+ ansprechpartner TEXT,
gehalt TEXT,
beschreibung TEXT,
quelle_url TEXT,
@@ -657,6 +659,8 @@ function initializeDatabase() {
// Migration: add columns to pre-existing jobangebote tables.
db.run('ALTER TABLE jobangebote ADD COLUMN anzeige_datum DATE', () => {});
db.run('ALTER TABLE jobangebote ADD COLUMN kontakt_email TEXT', () => {});
+ db.run('ALTER TABLE jobangebote ADD COLUMN adresse TEXT', () => {});
+ db.run('ALTER TABLE jobangebote ADD COLUMN ansprechpartner TEXT', () => {});
// Normalized URL for URL-based de-duplication of offers (see lib/blacklist).
db.run('ALTER TABLE jobangebote ADD COLUMN url_norm TEXT', () => {
// Backfill url_norm for rows ingested before this column existed.
@@ -1726,11 +1730,23 @@ initializeDatabase().then(() => {
angebot.quelle ? `Importiert via: ${angebot.quelle}` : null,
].filter(Boolean);
+ // Employer address (street, house number, city) and contact person go into
+ // the AI notes so the LLM can use them for the letter's Anschriftfeld and
+ // salutation.
+ let anschrift = (angebot.adresse || '').trim();
+ if (angebot.ort && !anschrift.toLowerCase().includes(String(angebot.ort).toLowerCase())) {
+ anschrift = anschrift ? `${anschrift}, ${angebot.ort}` : String(angebot.ort);
+ }
+ const llmNotizen = [
+ anschrift ? `Anschrift des Arbeitgebers: ${anschrift}` : null,
+ angebot.ansprechpartner ? `Ansprechpartner: ${angebot.ansprechpartner}` : null,
+ ].filter(Boolean).join('\n');
+
const result = await dbRun(
`INSERT INTO bewerbungen
(datum, firma, stelle, art, status, notizen, ort, stellenbeschreibung,
- quelle_url, email_empfaenger, generierung_status)
- VALUES (?, ?, ?, ?, 'Entwurf', ?, ?, ?, ?, ?, 'nicht_gestartet')`,
+ quelle_url, email_empfaenger, llm_notizen, generierung_status)
+ VALUES (?, ?, ?, ?, 'Entwurf', ?, ?, ?, ?, ?, ?, 'nicht_gestartet')`,
[
datum,
sanitizeInput(angebot.firma),
@@ -1741,6 +1757,7 @@ initializeDatabase().then(() => {
angebot.beschreibung || '',
angebot.quelle_url || '',
angebot.kontakt_email || '',
+ llmNotizen,
]
);
@@ -1799,7 +1816,7 @@ initializeDatabase().then(() => {
const b = req.body || {};
const quelleUrl = sanitizeInput(b.quelle_url || '');
await dbRun(
- `UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, gehalt = ?,
+ `UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, adresse = ?, ansprechpartner = ?, gehalt = ?,
beschreibung = ?, kontakt_email = ?, quelle_url = ?, anzeige_datum = ?,
url_norm = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
@@ -1807,6 +1824,8 @@ initializeDatabase().then(() => {
sanitizeInput((b.firma || '').trim()) || angebot.firma,
sanitizeInput((b.stelle || '').trim()) || angebot.stelle,
sanitizeInput(b.ort || ''),
+ sanitizeInput(b.adresse || ''),
+ sanitizeInput(b.ansprechpartner || ''),
sanitizeInput(b.gehalt || ''),
sanitizeInput(b.beschreibung || ''),
sanitizeInput(b.kontakt_email || ''),
diff --git a/views/bewerbung.ejs b/views/bewerbung.ejs
index 13c8e67..93102fc 100644
--- a/views/bewerbung.ejs
+++ b/views/bewerbung.ejs
@@ -360,7 +360,8 @@
Versand über <%= mailFrom %>. Empfängeradresse eingeben, Betreff und Text prüfen,
Anhänge auswählen und senden.
-