Guard against duplicate applications + capture jobs on any website

Duplicate safeguard: before creating an application (manual add and
browser import) the server checks for an existing one for the same job —
matched on a normalised source URL (job-id params like Indeed's jk pin
the posting across paths/tracking) or an identical company + role
(case/umlaut/whitespace-insensitive). On a match it returns 409 with the
matches; the web form and the extension show the existing entry and
re-submit with force=true only if the user confirms. Not a hard block, so
legitimate re-applications stay possible.

Universal capture: the extension popup becomes an editable capture form
that works on any site. It extracts the active page on demand (schema.org
JobPosting JSON-LD -> OpenGraph/meta -> h1/title/selection -> canonical
URL), lets the user review/correct, and sends. The import route is now
source-agnostic and derives the application source (art) from the URL
instead of hardcoding Indeed; the Indeed on-page button remains as a fast
path. Adds scripting/activeTab permissions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 18:32:17 +02:00
co-authored by Claude Opus 4.8
parent d8b552e57d
commit b1c92dd77b
7 changed files with 457 additions and 87 deletions
+41 -18
View File
@@ -189,29 +189,52 @@ function saveApplication(event) {
let url = '/api/bewerbungen';
let method = 'POST';
if (currentApplicationId) {
url = `/api/bewerbungen/${currentApplicationId}`;
method = 'PUT';
}
fetch(url, {
const send = (payload) => fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(application)
})
.then(response => response.json())
.then(data => {
if (data.success) {
hideModal(applicationModal);
resetApplicationForm();
// Refresh the page to show updated data
location.reload();
}
})
.catch(error => console.error('Error saving application:', error));
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
send(application)
.then(async (response) => {
// Duplicate guard: the server flags a likely double application (409).
if (response.status === 409) {
const info = await response.json().catch(() => ({}));
if (info.duplicate) {
if (confirm(duplicateWarning(info.matches))) {
return send(Object.assign({}, application, { force: true })).then(r => r.json());
}
return null; // user cancelled — keep the form open
}
}
return response.json();
})
.then(data => {
if (data && data.success) {
hideModal(applicationModal);
resetApplicationForm();
location.reload();
}
})
.catch(error => console.error('Error saving application:', error));
}
// Human-readable warning listing the existing application(s) that look like the
// same job, shown before a possible double application is created.
function duplicateWarning(matches) {
const lines = (matches || []).slice(0, 5).map(m => {
const parts = [m.firma, m.stelle].filter(Boolean).join(' — ');
const meta = [m.datum, m.status].filter(Boolean).join(', ');
return '• ' + parts + (meta ? ' (' + meta + ')' : '');
});
return 'Es gibt bereits eine passende Bewerbung:\n\n' + lines.join('\n') +
'\n\nTrotzdem eine weitere Bewerbung für diese Stelle anlegen?';
}
function openDeleteModal(id) {