Labels für Stellen (Bewerbungen + Jobangebote), inkl. API & Swagger

Mehrere Labels pro Stelle (Regional, Remote-Deutschlandweit,
Homeoffice-Deutschlandweit), gespeichert als JSON-Array in einer neuen
labels-Spalte beider Tabellen (Migration). Geteiltes lib/labels.js mit
parse/serialize; wiederverwendbare Partials fuer Chips + Mehrfachauswahl.

- Web: setzen im Hinzufuegen-Modal, auf der Bearbeiten-Seite und im
  Jobangebot-Bearbeiten-Formular; Anzeige als Chips in den Listen.
- Uebernahme eines Angebots traegt dessen Labels in die neue Bewerbung.
- REST-API: labels[] in /applications und /joboffers (GET/POST/PUT),
  Filter ?label=…; OpenAPI/Swagger-Schemas + Enums erweitert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 15:23:44 +02:00
co-authored by Claude Opus 4.8
parent d1c6743d2f
commit 03760d293f
10 changed files with 178 additions and 28 deletions
+14
View File
@@ -11,6 +11,14 @@ const STATUS_OPTIONS = [
'Entwurf', 'Gesendet', 'Eingangsbestätigung', 'Vorstellungsgespräch',
'Absage', 'Einstellung', 'Keine Rückmeldung',
];
const { LABEL_OPTIONS } = require('./labels');
// Reusable schema for the labels array (work-location labels of a Stelle).
const labelsSchema = {
type: 'array',
items: { type: 'string', enum: LABEL_OPTIONS },
description: 'Labels der Stelle (Arbeitsort). Mehrere möglich.',
};
// Shared reusable schemas -----------------------------------------------
@@ -79,6 +87,7 @@ function buildOpenApiSpec(baseUrl = '') {
{ name: 'year', in: 'query', schema: { type: 'string', pattern: '^[0-9]{4}$' }, description: 'Jahr (z. B. 2026)' },
{ name: 'status', in: 'query', schema: { type: 'string', enum: STATUS_OPTIONS }, description: 'Filter nach Status' },
{ name: 'art', in: 'query', schema: { type: 'string', enum: ART_OPTIONS }, description: 'Filter nach Bewerbungsart' },
{ name: 'label', in: 'query', schema: { type: 'string', enum: LABEL_OPTIONS }, description: 'Filter nach Label (Stellen mit diesem Label)' },
{ name: 'search', in: 'query', schema: { type: 'string' }, description: 'Freitextsuche in Firma und Stelle' },
{ name: 'limit', in: 'query', schema: { type: 'integer', minimum: 1, maximum: 500, default: 100 }, description: 'Max. Anzahl Ergebnisse' },
{ name: 'offset', in: 'query', schema: { type: 'integer', minimum: 0, default: 0 }, description: 'Ergebnis-Offset (Paging)' },
@@ -663,6 +672,7 @@ function buildOpenApiSpec(baseUrl = '') {
stelle: { type: 'string' },
art: { type: 'string', enum: ART_OPTIONS, nullable: true },
status: { type: 'string', enum: STATUS_OPTIONS, nullable: true },
labels: { ...labelsSchema, nullable: true },
notizen: { type: 'string', nullable: true },
interne_notizen: { type: 'string', nullable: true },
ort: { type: 'string', nullable: true },
@@ -692,6 +702,7 @@ function buildOpenApiSpec(baseUrl = '') {
stelle: { type: 'string' },
art: { type: 'string', enum: ART_OPTIONS },
status: { type: 'string', enum: STATUS_OPTIONS },
labels: labelsSchema,
notizen: { type: 'string' },
interne_notizen: { type: 'string' },
ort: { type: 'string' },
@@ -711,6 +722,7 @@ function buildOpenApiSpec(baseUrl = '') {
stelle: { type: 'string' },
art: { type: 'string', enum: ART_OPTIONS },
status: { type: 'string', enum: STATUS_OPTIONS },
labels: labelsSchema,
notizen: { type: 'string' },
interne_notizen: { type: 'string' },
ort: { type: 'string' },
@@ -871,6 +883,7 @@ function buildOpenApiSpec(baseUrl = '') {
anzeige_datum: { type: 'string', format: 'date', nullable: true, description: 'Datum der eigentlichen Stellenanzeige (vom Drittanbieter übergeben)' },
kontakt_email: { type: 'string', format: 'email', nullable: true, description: 'Kontakt-E-Mail-Adresse der Stelle' },
status: { type: 'string', enum: ['offen', 'uebernommen', 'abgelehnt'] },
labels: { ...labelsSchema, nullable: true },
verknuepfte_bewerbung_id: { type: 'integer', nullable: true },
url_norm: { type: 'string', nullable: true, description: 'Normalisierte URL für die De-Duplizierung (serverseitig gesetzt)' },
created_at: { type: 'string', format: 'date-time' },
@@ -895,6 +908,7 @@ function buildOpenApiSpec(baseUrl = '') {
anzeige_datum: { type: 'string', format: 'date', description: 'Datum der eigentlichen Stellenanzeige (ISO YYYY-MM-DD)' },
kontakt_email: { type: 'string', format: 'email', description: 'Kontakt-E-Mail-Adresse der Stelle' },
status: { type: 'string', enum: ['offen', 'uebernommen', 'abgelehnt'], default: 'offen' },
labels: labelsSchema,
},
},
JobOfferResult: {