Bug Fixes + Duplicate Support (#1016)

* Update README.md supporter list

* Fix: Multiple bug fixes and features bundle (#888, #991, #617, #984) (#1007)

* fix: resolve location creation failures, broken image uploads, and invalid URL handling

- Add missing addToast import in LocationDetails.svelte for proper error feedback
- Add objectId check and error response handling in ImageManagement.svelte to prevent ghost images
- Add Content-Type check in +page.server.ts image action to handle non-JSON backend responses
- Add client-side URL validation in LocationDetails.svelte (invalid URLs → null)
- Improve Django field error extraction for user-friendly toast messages
- Clean up empty description fields (whitespace → null)
- Update BUGFIX_DOCUMENTATION.md with detailed fix descriptions

* feat: bug fixes and new features bundle

Bug fixes:
- fix: resolve PATCH location with visits (#888)
- fix: Wikipedia/URL image upload via server-side proxy (#991)
- fix: private/public toggle race condition (#617)
- fix: location creation feedback (addToast import)
- fix: invalid URL handling for locations and collections
- fix: world map country highlighting (bg-*-200 -> bg-*-400)
- fix: clipboard API polyfill for HTTP contexts
- fix: MultipleObjectsReturned for duplicate images
- fix: SvelteKit proxy sessionid cookie forwarding

Features:
- feat: duplicate location button (list + detail view)
- feat: duplicate collection button
- feat: i18n translations for 19 languages
- feat: improved error handling and user feedback

Technical:
- Backend: fetch_from_url endpoint with SSRF protection
- Backend: validate_link() for collections
- Backend: file_permissions filter() instead of get()
- Frontend: copyToClipboard() helper function
- Frontend: clipboard polyfill via server-side injection

* chore: switch docker-compose from image to build

Use local source code builds instead of upstream :latest images
to preserve our custom patches and fixes.

* fix: lodging save errors, AI language support, and i18n improvements

- Fix Lodging save: add res.ok checks, error toasts, isSaving state (#984)
- Fix URL validation: silently set invalid URLs to null (Lodging, Transportation)
- Fix AI description language: pass user locale to Wikipedia API
- Fix missing i18n keys: Strava toggle buttons (show/hide)
- Add CHANGELOG.md
- Remove internal documentation from public tracking
- Update .gitignore for Cursor IDE and internal docs

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: update location duplication handling, improve UI feedback, and enhance localization support

---------

Co-authored-by: AdventureLog Bugfix <bugfix@adventurelog.local>
Co-authored-by: madmp87 <info@so-pa.de>
Co-authored-by: Mathias Ponnwitz <devuser@dockge-dev.fritz.box>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Sean Morley <mail@seanmorley.com>

* Enhance duplication functionality for collections and locations; update UI to reflect changes

* Potential fix for code scanning alert no. 49: Information exposure through an exception

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Update Django and Pillow versions in requirements.txt

* Fix error logging for image fetch timeout in ContentImageViewSet

* Update requirements.txt to include jaraco.context and wheel for security fixes

* Update app version and add security vulnerabilities to .trivyignore

* Update backend/server/adventures/views/collection_view.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update frontend/src/lib/types.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Reorder build and image directives in docker-compose.yml for clarity

* Refactor code structure for improved readability and maintainability

* Remove inline clipboard polyfill script injection from server hooks (#1019)

* Initial plan

* Remove inline clipboard polyfill script injection from hooks.server.ts

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* Fix unhandled promise rejections in copyToClipboard click handlers (#1018)

* Initial plan

* Fix: make copyToClipboard handlers async with try/catch error toast

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* Harden `fetch_from_url` image proxy: require auth, rate-limit, and strengthen SSRF protections (#1017)

* Initial plan

* Harden fetch_from_url: require auth, rate-limit, block non-standard ports, check all IPs, re-validate redirects

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* Fix subregion filtering in world travel page to exclude null values

* Update package.json to use caret (^) for versioning in overrides

* fix: update package dependencies for compatibility and stability

- Added cookie dependency with version constraint <0.7.0
- Updated svelte dependency to allow versions <=5.51.4
- Updated @sveltejs/adapter-vercel dependency to allow versions <6.3.2

* Refactor code structure for improved readability and maintainability

---------

Co-authored-by: madmp87 <79420509+madmp87@users.noreply.github.com>
Co-authored-by: AdventureLog Bugfix <bugfix@adventurelog.local>
Co-authored-by: madmp87 <info@so-pa.de>
Co-authored-by: Mathias Ponnwitz <devuser@dockge-dev.fritz.box>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Sean Morley
2026-02-20 20:49:24 -05:00
committed by GitHub
parent c008f0c264
commit bec90fe2a5
57 changed files with 21743 additions and 20304 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { t, locale } from 'svelte-i18n';
import { updateLocalDate, updateUTCDate, validateDateRange } from '$lib/dateUtils';
import type { Collection, Lodging, MoneyValue } from '$lib/types';
import LocationSearchMap from '../shared/LocationSearchMap.svelte';
@@ -20,9 +20,11 @@
// @ts-ignore
import { DateTime } from 'luxon';
import { isAllDay } from '$lib';
import { addToast } from '$lib/toasts';
const dispatch = createEventDispatcher();
let isSaving = false;
let isReverseGeocoding = false;
let initialSelection: {
@@ -249,7 +251,9 @@
try {
// Mock Wikipedia API call - replace with actual implementation
const response = await fetch(`/api/generate/desc/?name=${encodeURIComponent(lodging.name)}`);
const response = await fetch(
`/api/generate/desc/?name=${encodeURIComponent(lodging.name)}&lang=${$locale || 'en'}`
);
if (response.ok) {
const data = await response.json();
lodging.description = data.extract || '';
@@ -268,6 +272,9 @@
return;
}
// Prevent double-clicks while saving
if (isSaving) return;
// Ensure timezone is only persisted for timed stays
lodging.timezone = allDay ? null : selectedTimezone;
@@ -297,47 +304,83 @@
payload = normalizeMoneyPayload(payload, 'price', 'price_currency', preferredCurrency);
}
// Remove empty link to avoid URL validation errors
if (!payload.link || payload.link.trim() === '') {
delete payload.link;
// Clean up link: empty/whitespace → null, invalid URL → null
if (!payload.link || !payload.link.trim()) {
payload.link = null;
} else {
try {
new URL(payload.link);
} catch {
// Not a valid URL — clear it so Django doesn't reject it
payload.link = null;
}
}
// If we're editing and the original location had collection, but the form's collection
// is empty (i.e. user didn't modify collection), omit collection from payload so the
// server doesn't clear them unintentionally.
if (lodgingToEdit && lodgingToEdit.id) {
if (
(!payload.collection || payload.collection.length === 0) &&
lodgingToEdit.collection &&
lodgingToEdit.collection.length > 0
) {
delete payload.collection;
isSaving = true;
try {
// If we're editing and the original location had collection, but the form's collection
// is empty (i.e. user didn't modify collection), omit collection from payload so the
// server doesn't clear them unintentionally.
if (lodgingToEdit && lodgingToEdit.id) {
if (
(!payload.collection || payload.collection.length === 0) &&
lodgingToEdit.collection &&
lodgingToEdit.collection.length > 0
) {
delete payload.collection;
}
let res = await fetch(`/api/lodging/${lodgingToEdit.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!res.ok) {
const errorData = await res.json().catch(() => null);
const errorMsg = errorData
? Object.values(errorData).flat().join(', ')
: `Server error (${res.status})`;
addToast('error', errorMsg);
return;
}
let updatedLocation = await res.json();
lodging = updatedLocation;
} else {
let res = await fetch(`/api/lodging`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!res.ok) {
const errorData = await res.json().catch(() => null);
const errorMsg = errorData
? Object.values(errorData).flat().join(', ')
: `Server error (${res.status})`;
addToast('error', errorMsg);
return;
}
let newLodging = await res.json();
lodging = newLodging;
}
let res = await fetch(`/api/lodging/${lodgingToEdit.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
dispatch('save', {
...lodging
});
let updatedLocation = await res.json();
lodging = updatedLocation;
} else {
let res = await fetch(`/api/lodging`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
let newLodging = await res.json();
lodging = newLodging;
} catch (err) {
console.error('Error saving lodging:', err);
addToast('error', $t('lodging.save_failed') || 'Failed to save lodging. Please try again.');
} finally {
isSaving = false;
}
dispatch('save', {
...lodging
});
}
function handleBack() {
@@ -753,10 +796,13 @@
<div class="flex gap-3 justify-end pt-4">
<button
class="btn btn-primary gap-2"
disabled={!lodging.name || !lodging.type || isReverseGeocoding}
disabled={!lodging.name || !lodging.type || isReverseGeocoding || isSaving}
on:click={handleSave}
>
{#if isReverseGeocoding}
{#if isSaving}
<span class="loading loading-spinner loading-sm"></span>
{$t('adventures.saving') || 'Saving...'}
{:else if isReverseGeocoding}
<span class="loading loading-spinner loading-sm"></span>
{$t('adventures.processing')}...
{:else}