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:
+41
-18
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user