19 Commits
Author SHA1 Message Date
thomasandClaude d9ba8698e0 Termine: alle anstehenden auf Startseite + Bearbeiten; Status Telefonat
- Startseite zeigt alle anstehenden Termine (vorher auf 6 begrenzt)
- Termine lassen sich inline bearbeiten (CalDAV-Update, Neu-Anlage bei 404/412)
- Neuer Bewerbungsstatus Telefonat (Listen, Farben, API, PDF, Statistik)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 15:27:19 +02:00
thomasandClaude Opus 4.8 1dd4807ca6 Einstellungen: Vorauswahl für KI-Generierung (Dokumente + Anlagen)
Zwei neue, pro Benutzer gespeicherte Konfigurationswerte:
GEN_DOKUMENTE_DEFAULT (Standard: Anschreiben + Lebenslauf) und
GEN_ANLAGEN_DEFAULT (Standard: keine). Sie belegen die Häkchen auf der
Bewerbungsseite vor; eine bereits getroffene Dokumentenauswahl der
Bewerbung gewinnt weiterhin. Die REST-API nutzt die Vorauswahl, wenn
dokumente/anlagen im Body fehlen.

Die Einstellungsseite kennt dafür jetzt Checkbox-Gruppen, deren Optionen
auch erst zur Renderzeit feststehen dürfen (die eigenen Anlagen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:31:19 +02:00
thomasandClaude Opus 4.8 75813a38a4 Jobangebote: Favoriten-Funktion mit eigenem "Gemerkt"-Tab
Jobangebote lassen sich per Stern merken — eigene Spalte `favorit`,
getrennt von den Bewerbungs-Favoriten. Gemerkte Angebote stehen oben in
der offenen Liste und haben einen eigenen Tab, der Favoriten quer über
alle Status zeigt und ausgeblendet bleibt, solange es keine gibt.

Der Stern wird nur in der Weboberfläche gesetzt; ein erneutes Einspielen
desselben Angebots über die REST-API überschreibt ihn nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:45:04 +02:00
thomasandClaude Opus 4.8 e462fa18ad Beispieldaten entpersonalisiert: generische Staedte statt Gladbeck & Co.
Platzhalter und Doku-Beispiele nannten durchgehend den Wohnort des
Entwicklers (Gladbeck, Bottrop, Gelsenkirchen, Essen), dessen Vorwahl
(02043), PLZ und Strasse. Ersetzt durch Berlin/Hamburg/Muenchen sowie eine
zum Beispielort passende Vorwahl (030). Betrifft die Platzhalter fuer
Telefon/Ort (Vorlagen), die Staedte-Eingabe (Jobsuche), das Adressbeispiel
in den LLM-Notizen, die OpenAPI-Beispiele und zwei Code-Kommentare.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:14:06 +02:00
thomasandClaude Opus 4.8 5ce835ff3a Bewerbungsstatus "Vertragsverhandlung" ergaenzt
Neue Station zwischen Vorstellungsgespraech und Einstellung - in der
Statusliste, der Gruppierung auf der Startseite, den Farbskalen (Lime,
zwischen dem Gelb des Gespraechs und dem Gruen der Einstellung), im
PDF-Export, in der Statistik und in der REST-API/OpenAPI.

Der Statistik-Funnel zaehlte bisher ever('Vorstellungsgespraech') +
ever('Einstellung') und damit eine Bewerbung, die beide Stationen
durchlaufen hat, doppelt. Mit einer weiteren Stufe waere das schlimmer
geworden, deshalb zaehlt der Funnel jetzt pro Bewerbung statt pro
Statuszeile: jede Stufe zaehlt eine Bewerbung genau einmal, ein spaeterer
Status impliziert die frueheren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:10:02 +02:00
thomasandClaude bf789af62a Rebranding zu NextJobs (nextjobs.cc)
- Markenname 'Bewerbungs-Tracker' -> 'NextJobs' im gesamten UI,
  Footer (incl. nextjobs.cc Copyright), Login, Wortmarke in der
  Top-Navi (Next + Jobs-Gradient), Favicon, Swagger/OpenAPI-Titel,
  CalDAV-PRODID, Browser-Erweiterung (manifest, content, background),
  Runner-Log und Doku.
- Funktionale Begriffe (Bewerbungsunterlagen, -datum etc.) bleiben.
- Docker-Registry (git.hackner.dev) als Infrastruktur unberuehrt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:40:26 +02:00
thomasandClaude Opus 4.8 1e14738258 API: Zugriff strikt auf den Besitzer des API-Keys, Key generieren/anzeigen
Der X-API-Key identifiziert den Benutzer; alle Endpunkte waren bereits auf
dessen user_id gescoped (27 Operationen geprueft). Zwei Luecken blieben:

- Die Aufloesung Token -> Benutzer nahm bei mehrdeutigem Token per LIMIT 1
  einfach den ersten Treffer. Haetten zwei Benutzer denselben Token, saehe
  der eine die Daten des anderen. Jetzt: fail closed (401 + Logeintrag),
  und /einstellungen weist einen bereits vergebenen Token mit 409 ab.
- Ein leerer Token galt als Wert: wer seine Einstellungen einmal gespeichert
  hatte, besass eine API_TOKEN-Zeile mit ''. Leere Werte matchen jetzt nie.

Neu in den Einstellungen (Abschnitt REST-API):
- "Neu generieren" erzeugt einen zufaelligen Token (32 Byte, crypto.get-
  RandomValues); er wird nur ins Feld gefuellt und erst beim Speichern
  aktiv, ein Fehlklick laesst sich also verwerfen.
- "Kopieren" legt den Token in die Zwischenablage; das Auge blendet ihn ein
  (bestand bereits fuer Secret-Felder).

API aktualisiert:
- PUT /settings kannte nur name/adresse/kundennummer, GET lieferte aber alle
  acht Felder. Jetzt schreibt PUT alle (email, telefon, ort, webseite,
  geburtsdatum) und aendert nur die im Body uebergebenen Felder; die Antwort
  enthaelt den neuen Stand.

Swagger:
- Beschreibung sagte "konfiguriert via Umgebungsvariable API_TOKEN" - das
  gilt seit der Multi-User-Umstellung nicht mehr. Jetzt dokumentiert:
  Token pro Benutzer aus den Einstellungen, Zugriff nur auf eigene Daten,
  fremde id -> 404, unbekannter/leerer/mehrdeutiger Token -> 401.
- Settings-Schema um die fehlenden fuenf Felder ergaenzt.

Verifiziert mit zwei Benutzern und je eigenem Key: Lesen, Aendern, Loeschen,
Timeline, Anhaenge, E-Mails, Generierung, Jobangebote und Blacklist des
jeweils anderen liefern durchgaengig 404; Listen, Export und Statistik
zeigen nur eigene Daten; kollidierender Token -> 409; mehrdeutiger Token in
der DB -> 401 fuer beide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:56:21 +02:00
thomasandClaude 41887dd56c Status "Absage von meiner Seite" ergaenzt
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 12:51:37 +02:00
thomasandClaude Opus 4.8 d1116d522d Vor dem Generieren waehlen: Anschreiben, Lebenslauf oder beides
Auf der Bewerbungsseite laesst sich jetzt auswaehlen, welche Unterlagen die KI
erzeugt. Standard bleibt beides; die letzte Auswahl wird gemerkt und ist beim
naechsten Generieren vorbelegt. Ohne Haken bricht das Formular ab statt still
beides zu erzeugen.

Nicht gewaehlte Dokumente werden gar nicht erst angefragt: JSON-Schema und
Skelett im Prompt werden auf die gewuenschten Teile reduziert, das spart einen
Gutteil der Generierungszeit. Gerendert wird ebenfalls nur das Gewaehlte - auch
wenn das Modell sich nicht an das Schema haelt und trotzdem alles liefert.

Fuer den E-Mail-Versand mitgedacht:
- Wird kein Lebenslauf erzeugt, fuehrt ihn das Anschreiben nicht mehr unter
  "Anlagen" auf. Sonst kuendigt der Brief eine Anlage an, die nie mitgeht.
- Der Prompt bekommt zusaetzlich die Liste der Dateien, die der Bewerbungsmail
  tatsaechlich anhaengen (inkl. Anschreiben selbst), damit der Begleittext keine
  Unterlagen nennt, die nicht dabei sind.
- Die Anhang-Checkboxen im Mailformular ziehen sich aus den erzeugten Anhaengen,
  greifen also automatisch.

Die REST-API kennt das Feld ebenfalls (POST .../generate, "dokumente"), inkl.
OpenAPI-Doku.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:18:02 +02:00
thomasandClaude Opus 4.8 22f967c753 Carry firma_slug on job offers, supplied by the ingesting client
The blacklist company slug now lives on the offers themselves. POST /joboffers
accepts firma_slug and stores it as supplied (deriving it from firma only when
omitted); it is persisted on jobangebote (schema + ALTER/backfill + index) and
returned in the Joboffer response. Blacklist matching prefers the client-supplied
slug over one derived from firma, so the indexing client controls the identity
used to block an offer. OpenAPI request/response schemas updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:42:41 +02:00
thomasandClaude Opus 4.8 f935e00b53 Add robust company slug for job-offer blacklist matching
Company matching relied on normText(), which kept legal-form suffixes,
spacing and umlaut spelling — so "Bosch GmbH", "Bosch AG" and "bosch gmbh"
did not match the same entry, making the blacklist unreliable.

Introduce firmaSlug(): transliterate German umlauts, strip diacritics and
trailing legal-form tokens (GmbH/AG/SE/KG/…), then kebab-join. Add a
firma_slug field to jobangebote_blacklist (schema + ALTER/backfill migration)
and match on it for typ firma/firma_stelle/auto, falling back to firma_norm
for legacy rows. POST /joboffers rejects blacklisted offers via matchBlacklist
automatically since offerSignature now carries the slug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:25:59 +02:00
thomasandClaude Opus 4.8 c2c51f9fca Neue Bewerbungs-Status: In Bearbeitung, Interessiert, Warten auf Rückmeldung/meine Antwort
Vier mittlere Pipeline-Status ergänzt (nach Eingangsbestätigung, vor
Vorstellungsgespräch), inkl. Farben/Reihenfolge in Übersicht, Detailseite
und PDF-Export sowie REST-API-Validierung und Swagger-Enum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:06:56 +02:00
thomasandClaude Opus 4.8 03760d293f 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>
2026-07-05 15:23:44 +02:00
thomasandClaude Opus 4.8 26872ece08 Choose enclosed attachments before generating; drop instant-generate
The generate form now lets the user pick which extra attachments
(basis_anhaenge) to enclose — none selected by default. Only the chosen
ones are attached, and their names are passed to the LLM so the cover
letter's "Anlagen" list and wording reflect exactly what is enclosed.

Job offers keep only "Als Bewerbung übernehmen" (draft first); the
"Übernehmen & KI-Unterlagen" button and its route are removed.

REST API: POST /applications/:id/generate accepts an optional `anlagen`
array of attachment IDs (Swagger updated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:36:14 +02:00
thomasandClaude Opus 4.8 360e04d677 Confirm before sending; add address + contact to job offers
"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 <noreply@anthropic.com>
2026-07-04 17:10:13 +02:00
thomasandClaude Opus 4.8 dae077486f Add a job-offer blacklist with URL-based de-duplication
Offers are now de-duplicated by normalized URL (tracking params stripped)
in addition to (quelle, external_id), so the same posting never lands
twice — even re-scraped under a new id. Deleting an offer (web or API)
auto-blacklists it, so it can never reappear.

lib/blacklist.js provides shared normalization + matching. Manual entries
can block a URL, a whole domain, a company, or a company+title posting
(gender-marker tolerant). New /blacklist page lists and manages entries.

REST API: GET/POST /joboffers/blacklist, DELETE /joboffers/blacklist/{id};
POST /joboffers returns 409 when blacklisted; DELETE /joboffers/{id}
auto-blacklists (opt out with ?blacklist=false). Swagger updated with the
new paths and schemas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 13:33:12 +02:00
thomasandClaude cb1115ef95 Add kontakt_email to job offers
- New kontakt_email TEXT column on jobangebote (+ ALTER TABLE migration)
- Accept kontakt_email in POST /api/v1/joboffers (create + upsert) and
  document it in the OpenAPI spec
- Render a mailto: link on the /jobangebote page and include it in the
  curl example

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 22:16:55 +02:00
thomasandClaude 5d6fed7041 Show job offer posting date (anzeige_datum) alongside ingestion date
- 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: <anzeige_datum>" and
  "Einspielung: <created_at>"

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:46:12 +02:00
thomasandClaude 88875dbc33 Add third-party REST API (/api/v1) + Swagger UI and Jobangebote page
- REST API under /api/v1 with X-API-Key auth (API_TOKEN), documented with
  OpenAPI 3.0; Swagger UI at /swagger, spec at /swagger.json
- Endpoints: applications CRUD + timeline, attachments/emails download,
  generation trigger/status, settings, statistics, export, templates, joboffers
- Jobangebote page (/jobangebote) listing offers ingested via the REST API,
  with "Als Bewerbung übernehmen" and delete actions; header nav + badge
- jobangebote table with (quelle, external_id) upsert for third-party ingestion

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:30:33 +02:00