Fix 7 bugs: security, memory leak, stale state, error handling
- WebSocket: verify task ownership before allowing subscribe (security) - POI routes: replace assert with HTTPException for production safety - cancel_task: return HTTP 404 instead of 200 for missing tasks - routing_config: add descriptive ValueError for invalid env vars - POIManager: show error feedback instead of silently swallowing failures - VisualizationCard: reset POI/travel mode state on metric switch - Map: clean up heatmap layers/sources on unmount to prevent memory leak - Update test to expect 404 from cancel_task ownership check
This commit is contained in:
parent
25c87da1cf
commit
41b7d221e4
8 changed files with 45 additions and 9 deletions
|
|
@ -194,6 +194,19 @@ export function Map(props: MapProps) {
|
|||
if (updateTimeoutRef.current) {
|
||||
clearTimeout(updateTimeoutRef.current);
|
||||
}
|
||||
// Remove heatmap layers and sources before destroying the map
|
||||
if (heatmapRef.current && mapRef.current) {
|
||||
for (const layerId of ['hexgrid-heatmap', 'hexgrid-heatmap-back']) {
|
||||
if (mapRef.current.getLayer(layerId)) {
|
||||
mapRef.current.removeLayer(layerId);
|
||||
}
|
||||
}
|
||||
for (const sourceId of ['hexgrid-heatmap', 'hexgrid-heatmap-back']) {
|
||||
if (mapRef.current.getSource(sourceId)) {
|
||||
mapRef.current.removeSource(sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
heatmapRef.current = null;
|
||||
isMapLoadedRef.current = false;
|
||||
mapRef.current?.remove();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export function POIManager({ user, listingType, onTaskCreated, pickedLocation, o
|
|||
const [lng, setLng] = useState('');
|
||||
const [calculating, setCalculating] = useState<number | null>(null);
|
||||
const [selectedModes, setSelectedModes] = useState<string[]>(['WALK', 'BICYCLE', 'TRANSIT']);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserPOIs(user).then(setPois).catch(() => {});
|
||||
|
|
@ -38,6 +39,7 @@ export function POIManager({ user, listingType, onTaskCreated, pickedLocation, o
|
|||
|
||||
const handleCreate = async () => {
|
||||
if (!name || !lat || !lng) return;
|
||||
setError(null);
|
||||
try {
|
||||
const poi = await createPOI(user, {
|
||||
name,
|
||||
|
|
@ -62,16 +64,17 @@ export function POIManager({ user, listingType, onTaskCreated, pickedLocation, o
|
|||
// Non-fatal: POI created successfully, calculation can be retried manually.
|
||||
}
|
||||
} catch {
|
||||
// silently fail
|
||||
setError('Failed to create POI. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (poiId: number) => {
|
||||
setError(null);
|
||||
try {
|
||||
await deletePOI(user, poiId);
|
||||
setPois(prev => prev.filter(p => p.id !== poiId));
|
||||
} catch {
|
||||
// silently fail
|
||||
setError('Failed to delete POI. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -95,6 +98,12 @@ export function POIManager({ user, listingType, onTaskCreated, pickedLocation, o
|
|||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{error && (
|
||||
<div className="text-xs text-destructive bg-destructive/10 border border-destructive/20 rounded-md px-3 py-2 flex items-center justify-between">
|
||||
<span>{error}</span>
|
||||
<button type="button" className="ml-2 text-destructive hover:text-destructive/80" onClick={() => setError(null)}>×</button>
|
||||
</div>
|
||||
)}
|
||||
{/* POI List */}
|
||||
{pois.map(poi => (
|
||||
<div key={poi.id} className="flex items-center gap-2 p-2 rounded-md border text-sm">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ export function VisualizationCard({ metric, onMetricChange, userPOIs, onPoiMetri
|
|||
onValueChange={(value) => {
|
||||
onMetricChange(value as Metric);
|
||||
if (value !== Metric.poi_travel) {
|
||||
setSelectedPoiId('');
|
||||
setSelectedTravelMode('');
|
||||
onPoiMetricChange?.(null);
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue