* 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>
132 lines
4.0 KiB
TypeScript
132 lines
4.0 KiB
TypeScript
import { fail, redirect } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types';
|
|
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
|
import type { Location, Collection, SlimCollection } from '$lib/types';
|
|
|
|
import type { Actions } from '@sveltejs/kit';
|
|
import { fetchCSRFToken } from '$lib/index.server';
|
|
import { checkLink } from '$lib';
|
|
|
|
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
|
|
|
export const load = (async (event) => {
|
|
if (!event.locals.user) {
|
|
return redirect(302, '/login');
|
|
}
|
|
|
|
const sessionId = event.cookies.get('sessionid');
|
|
if (!sessionId) {
|
|
return redirect(302, '/login');
|
|
}
|
|
|
|
// Get sorting and filtering parameters from URL
|
|
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
|
|
const order_direction = event.url.searchParams.get('order_direction') || 'desc';
|
|
const status = event.url.searchParams.get('status') || '';
|
|
const page = event.url.searchParams.get('page') || '1';
|
|
const currentPage = parseInt(page);
|
|
|
|
// Common headers for all requests
|
|
const headers = {
|
|
Cookie: `sessionid=${sessionId}`
|
|
};
|
|
|
|
// Build API URL with nested=true for lighter payload
|
|
const statusParam = status ? `&status=${status}` : '';
|
|
const apiUrl = `${serverEndpoint}/api/collections/?order_by=${order_by}&order_direction=${order_direction}&page=${page}&nested=true${statusParam}`;
|
|
|
|
try {
|
|
// Execute all API calls in parallel
|
|
const [collectionsRes, sharedRes, archivedRes, invitesRes] = await Promise.all([
|
|
fetch(apiUrl, { headers, credentials: 'include' }),
|
|
fetch(`${serverEndpoint}/api/collections/shared/?nested=true`, { headers }),
|
|
fetch(`${serverEndpoint}/api/collections/archived/?nested=true`, { headers }),
|
|
fetch(`${serverEndpoint}/api/collections/invites/`, { headers })
|
|
]);
|
|
|
|
// Check if main collections request failed (most critical)
|
|
if (!collectionsRes.ok) {
|
|
console.error('Failed to fetch collections:', collectionsRes.status);
|
|
return redirect(302, '/login');
|
|
}
|
|
|
|
// Parse responses in parallel
|
|
const [collectionsData, sharedData, archivedData, invitesData] = await Promise.all([
|
|
collectionsRes.json(),
|
|
sharedRes.ok ? sharedRes.json() : [],
|
|
archivedRes.ok ? archivedRes.json() : [],
|
|
invitesRes.ok ? invitesRes.json() : []
|
|
]);
|
|
|
|
return {
|
|
props: {
|
|
adventures: collectionsData.results as Location[],
|
|
next: collectionsData.next,
|
|
previous: collectionsData.previous,
|
|
count: collectionsData.count,
|
|
sharedCollections: sharedData as SlimCollection[],
|
|
currentPage,
|
|
order_by,
|
|
order_direction,
|
|
status,
|
|
archivedCollections: archivedData as SlimCollection[],
|
|
invites: invitesData
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error);
|
|
return redirect(302, '/login');
|
|
}
|
|
}) satisfies PageServerLoad;
|
|
|
|
export const actions: Actions = {
|
|
restoreData: async (event) => {
|
|
if (!event.locals.user) {
|
|
return redirect(302, '/');
|
|
}
|
|
let sessionId = event.cookies.get('sessionid');
|
|
if (!sessionId) {
|
|
return redirect(302, '/');
|
|
}
|
|
try {
|
|
const formData = await event.request.formData();
|
|
const file = formData.get('file') as File | null | undefined;
|
|
|
|
if (!file || file.size === 0) {
|
|
return fail(400, { message: 'settings.no_file_selected' });
|
|
}
|
|
|
|
let csrfToken = await fetchCSRFToken();
|
|
|
|
// Create FormData for the API request
|
|
const apiFormData = new FormData();
|
|
apiFormData.append('file', file);
|
|
|
|
let res = await fetch(`${serverEndpoint}/api/collections/import/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Referer: event.url.origin,
|
|
Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`,
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
body: apiFormData
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const errorData = await res.json();
|
|
return fail(res.status, {
|
|
message: errorData.code
|
|
? `settings.restore_error_${errorData.code}`
|
|
: 'settings.generic_error',
|
|
details: errorData
|
|
});
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Restore error:', error);
|
|
return fail(500, { message: 'settings.generic_error' });
|
|
}
|
|
}
|
|
};
|