From 5d6fed70417a6bc99a71d6ffcb8d8b51f5c8c621 Mon Sep 17 00:00:00 2001 From: Thomas Hackner Date: Fri, 3 Jul 2026 21:46:12 +0200 Subject: [PATCH] Show job offer posting date (anzeige_datum) alongside ingestion date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add anzeige_datum DATE column to jobangebote (with ALTER TABLE migration for pre-existing tables) — the original posting date supplied by the third party - Accept anzeige_datum in POST /api/v1/joboffers (create + upsert) and document it in the OpenAPI spec - Render both on /jobangebote: "Anzeige: " and "Einspielung: " Co-Authored-By: Claude --- lib/api.js | 8 +++++--- lib/openapi.js | 2 ++ server.js | 3 +++ views/jobangebote.ejs | 16 ++++++++++++---- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/api.js b/lib/api.js index 6f58352..79d1e89 100644 --- a/lib/api.js +++ b/lib/api.js @@ -536,6 +536,7 @@ function createExternalApi(deps) { } const quelle = sanitizeInput(b.quelle || 'drittanbieter'); const externalId = b.external_id != null ? sanitizeInput(String(b.external_id)) : null; + const anzeigeDatum = sanitizeInput(b.anzeige_datum || ''); const fields = [ sanitizeInput(b.firma), sanitizeInput(b.stelle), @@ -544,6 +545,7 @@ function createExternalApi(deps) { sanitizeInput(b.beschreibung || ''), sanitizeInput(b.quelle_url || ''), sanitizeInput(b.art || ''), + anzeigeDatum, sanitizeInput(b.status || 'offen'), ]; @@ -556,7 +558,7 @@ function createExternalApi(deps) { if (existing) { await dbRun( `UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, gehalt = ?, - beschreibung = ?, quelle_url = ?, art = ?, status = ?, + beschreibung = ?, quelle_url = ?, art = ?, anzeige_datum = ?, status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, [...fields, existing.id] ); @@ -567,8 +569,8 @@ function createExternalApi(deps) { const result = await dbRun( `INSERT INTO jobangebote - (external_id, quelle, firma, stelle, ort, gehalt, beschreibung, quelle_url, art, status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + (external_id, quelle, firma, stelle, ort, gehalt, beschreibung, quelle_url, art, anzeige_datum, status) + 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 63f43c2..719a8f6 100644 --- a/lib/openapi.js +++ b/lib/openapi.js @@ -784,6 +784,7 @@ function buildOpenApiSpec(baseUrl = '') { beschreibung: { type: 'string', nullable: true }, quelle_url: { type: 'string', nullable: true }, art: { type: 'string', nullable: true }, + anzeige_datum: { type: 'string', format: 'date', nullable: true, description: 'Datum der eigentlichen Stellenanzeige (vom Drittanbieter übergeben)' }, status: { type: 'string', enum: ['offen', 'uebernommen', 'abgelehnt'] }, verknuepfte_bewerbung_id: { type: 'integer', nullable: true }, created_at: { type: 'string', format: 'date-time' }, @@ -803,6 +804,7 @@ function buildOpenApiSpec(baseUrl = '') { beschreibung: { type: 'string', description: 'Stellenbeschreibungstext' }, quelle_url: { type: 'string' }, art: { type: 'string', enum: ART_OPTIONS }, + anzeige_datum: { type: 'string', format: 'date', description: 'Datum der eigentlichen Stellenanzeige (ISO YYYY-MM-DD)' }, status: { type: 'string', enum: ['offen', 'uebernommen', 'abgelehnt'], default: 'offen' }, }, }, diff --git a/server.js b/server.js index 42ba1f0..59f6ffd 100644 --- a/server.js +++ b/server.js @@ -619,6 +619,7 @@ function initializeDatabase() { beschreibung TEXT, quelle_url TEXT, art TEXT, + anzeige_datum DATE, status TEXT NOT NULL DEFAULT 'offen', verknuepfte_bewerbung_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, @@ -627,6 +628,8 @@ function initializeDatabase() { FOREIGN KEY (verknuepfte_bewerbung_id) REFERENCES bewerbungen(id) ON DELETE SET NULL ) `); + // Migration: add anzeige_datum to pre-existing jobangebote tables. + db.run('ALTER TABLE jobangebote ADD COLUMN anzeige_datum DATE', () => {}); // Remember the last recipient address per application (prefill). db.run('ALTER TABLE bewerbungen ADD COLUMN email_empfaenger TEXT', () => {}); diff --git a/views/jobangebote.ejs b/views/jobangebote.ejs index 48bc227..49ef7cf 100644 --- a/views/jobangebote.ejs +++ b/views/jobangebote.ejs @@ -71,10 +71,17 @@ <%= j.quelle_url %> <% } %> -

- Eingegangen: <%= j.created_at ? new Date(j.created_at + 'Z').toLocaleString('de-DE') : '' %> +

+ <% if (j.anzeige_datum) { %> + + Anzeige: <%= new Date(j.anzeige_datum).toLocaleDateString('de-DE') %> + + <% } %> + + Einspielung: <%= j.created_at ? new Date(j.created_at + 'Z').toLocaleString('de-DE') : '' %> + <% if (j.verknuepfte_bewerbung_id) { %> - · Bewerbung #<%= j.verknuepfte_bewerbung_id %> + · Bewerbung #<%= j.verknuepfte_bewerbung_id %> <% } %>

@@ -143,7 +150,8 @@ "gehalt": "55.000 €", "beschreibung": "…", "quelle_url": "https://acme.example/job/12345", - "art": "Online-Portal" + "art": "Online-Portal", + "anzeige_datum": "2026-07-01" }'

Wiederholtes Senden mit derselben quelle + external_id aktualisiert das Angebot (Upsert). Die Doku/Swagger-UI liegt unter /swagger.