U4CVCPSGPGYWJ4PA72HHHCHKJSQW3GU2QFK4YFSMKQOEM2MMY3PQC OWHNUYOKSGQTG6ZSVQZ3DNMYCW3IOEMXGRRS5QRRDAPIG4MOODNAC UCDTBEK3CF6YT2H6V57HI6FAFW44BIYYAK3Z2QJ5LJE7QWX7OEYAC YX7LU4WRAUDMWS3DEDXZDSF6DXBHLYDWVSMSRK6KIW3MO6GRXSVQC RLH37YB4D7O42IFM2T7GJG4AVVAURWBZ7AOTHAWR7YJZRG3JOPLQC 4M3EBLTLSS2BRCM42ZP7WVD4YMRRLGV2P2XF47IAV5XHHJD52HTQC POIBWSL3JFHT2KN3STFSJX3INSYKEJTX6KSW3N7BVEKWX2GJ6T7QC details: error instanceof Error ? error.message : String(error),},500);}});/*** Protected API route to update an existing location** @route PUT /api/locations/:id* @authentication Required* @param {string} id - Location ID in URL path* @param {Object} body - Location data to update (name, description, latitude, longitude, active)* @returns {Object} Response containing:* - data: Updated location object* @description Updates an existing location. Requires EDIT permission on the dataset.*/app.put("/api/locations/:id", authenticate, async (c) => {try {// Get user from JWTconst jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;const userId = jwtPayload.sub;// Get location ID from URL parametersconst locationId = c.req.param("id");// Parse request bodyconst body = await c.req.json();const { name, description, latitude, longitude, active } = body;// Connect to databaseconst sql = neon(c.env.DATABASE_URL);const db = drizzle(sql);// Check if location exists and get its dataset IDconst existingLocation = await db.select({id: location.id,datasetId: location.datasetId,active: location.active}).from(location).where(eq(location.id, locationId)).limit(1);if (existingLocation.length === 0) {return c.json({error: "Location not found"}, 404);}const locationRecord = existingLocation[0];// Check if user has EDIT permission on the datasetconst hasEditPermission = await checkUserPermission(db, userId, locationRecord.datasetId, 'EDIT');if (!hasEditPermission) {return c.json({error: "You don't have permission to edit this location"}, 403);}// Build update object with only provided fieldsconst updateData: Record<string, unknown> = {lastModified: new Date(),modifiedBy: userId,};// Validate and add name if providedif (name !== undefined) {if (typeof name !== 'string' || name.trim().length === 0) {return c.json({error: "Field 'name' must be a non-empty string"}, 400);}if (name.length > 140) {return c.json({error: "Field 'name' must be 140 characters or less"}, 400);}updateData.name = name.trim();}// Validate and add description if providedif (description !== undefined) {if (description !== null && typeof description !== 'string') {return c.json({error: "Field 'description' must be a string or null"}, 400);}if (description && description.length > 255) {return c.json({error: "Field 'description' must be 255 characters or less"}, 400);}updateData.description = description?.trim() || null;}// Validate and add latitude if providedif (latitude !== undefined) {if (latitude !== null) {const lat = Number(latitude);if (isNaN(lat) || lat < -90 || lat > 90) {return c.json({error: "Field 'latitude' must be a valid number between -90 and 90"}, 400);}updateData.latitude = String(lat);} else {updateData.latitude = null;}}// Validate and add longitude if providedif (longitude !== undefined) {if (longitude !== null) {const lng = Number(longitude);if (isNaN(lng) || lng < -180 || lng > 180) {return c.json({error: "Field 'longitude' must be a valid number between -180 and 180"}, 400);}updateData.longitude = String(lng);} else {updateData.longitude = null;}}// Add active status if provided (for soft delete)if (active !== undefined) {if (typeof active !== 'boolean') {return c.json({error: "Field 'active' must be a boolean"}, 400);}updateData.active = active;}// Update the locationconst result = await db.update(location).set(updateData).where(eq(location.id, locationId)).returning({id: location.id,datasetId: location.datasetId,name: location.name,description: location.description,latitude: location.latitude,longitude: location.longitude,createdAt: location.createdAt,createdBy: location.createdBy,lastModified: location.lastModified,modifiedBy: location.modifiedBy,active: location.active,});if (result.length === 0) {return c.json({error: "Failed to update location"}, 500);}console.log("Updated location:", result[0].id, "by user:", userId);return c.json({data: result[0]});} catch (error) {console.error("Error updating location:", error);return c.json({error: "Failed to update location",
const response = await fetch("/api/locations", {method: "POST",headers: {"Authorization": `Bearer ${accessToken}`,"Content-Type": "application/json",},body: JSON.stringify({id,datasetId,name: formData.name,description: formData.description || null,latitude: formData.latitude ? parseFloat(formData.latitude) : null,longitude: formData.longitude ? parseFloat(formData.longitude) : null,}),});
if (isEditMode && editingLocation) {// Update existing locationconst response = await fetch(`/api/locations/${editingLocation.id}`, {method: "PUT",headers: {"Authorization": `Bearer ${accessToken}`,"Content-Type": "application/json",},body: JSON.stringify({name: formData.name,description: formData.description || null,latitude: formData.latitude ? parseFloat(formData.latitude) : null,longitude: formData.longitude ? parseFloat(formData.longitude) : null,}),});
if (!response.ok) {const errorData = await response.json();throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);}
if (!response.ok) {const errorData = await response.json();throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);}
const { data: newLocation } = await response.json();// Add the new location to the listsetLocations(prev => [newLocation, ...prev]);
const { data: updatedLocation } = await response.json();// Update the location in the listsetLocations(prev => prev.map(location =>location.id === updatedLocation.id ? updatedLocation : location));} else {// Create new locationconst id = nanoid(12);const response = await fetch("/api/locations", {method: "POST",headers: {"Authorization": `Bearer ${accessToken}`,"Content-Type": "application/json",},body: JSON.stringify({id,datasetId,name: formData.name,description: formData.description || null,latitude: formData.latitude ? parseFloat(formData.latitude) : null,longitude: formData.longitude ? parseFloat(formData.longitude) : null,}),});if (!response.ok) {const errorData = await response.json();throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);}const { data: newLocation } = await response.json();// Add the new location to the listsetLocations(prev => [newLocation, ...prev]);}
setFormError(err instanceof Error ? err.message : "Failed to create location");console.error("Error creating location:", err);
setFormError(err instanceof Error ? err.message : `Failed to ${isEditMode ? 'update' : 'create'} location`);console.error(`Error ${isEditMode ? 'updating' : 'creating'} location:`, err);
const handleEditLocation = (location: Location) => {setIsEditMode(true);setEditingLocation(location);setFormData({name: location.name,description: location.description || '',latitude: location.latitude ? String(location.latitude) : '',longitude: location.longitude ? String(location.longitude) : ''});setShowForm(true);};const handleDeleteLocation = async (location: Location) => {if (!isAuthenticated) return;const confirmDelete = window.confirm(`Are you sure you want to delete "${location.name}"? This action cannot be undone.`);if (!confirmDelete) return;try {const accessToken = await getAccessToken();const response = await fetch(`/api/locations/${location.id}`, {method: "PUT",headers: {"Authorization": `Bearer ${accessToken}`,"Content-Type": "application/json",},body: JSON.stringify({active: false}),});if (!response.ok) {const errorData = await response.json();throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);}// Remove the location from the list (soft delete)setLocations(prev => prev.filter(l => l.id !== location.id));} catch (err) {setError(err instanceof Error ? err.message : "Failed to delete location");console.error("Error deleting location:", err);}};
<TableCell className="font-medium whitespace-normal break-words">{location.name}</TableCell><TableCell className="whitespace-normal break-words">
<TableCellclassName={`font-medium whitespace-normal break-words ${onLocationSelect ? 'cursor-pointer' : ''}`}onClick={() => onLocationSelect && onLocationSelect(location.id, location.name)}>{location.name}</TableCell><TableCellclassName={`whitespace-normal break-words ${onLocationSelect ? 'cursor-pointer' : ''}`}onClick={() => onLocationSelect && onLocationSelect(location.id, location.name)}>
<TableCell className="whitespace-normal break-words">{location.description || "—"}</TableCell>
<TableCellclassName={`whitespace-normal break-words ${onLocationSelect ? 'cursor-pointer' : ''}`}onClick={() => onLocationSelect && onLocationSelect(location.id, location.name)}>{location.description || "—"}</TableCell>{canCreateLocations && (<TableCell className="text-center"><div className="flex justify-center gap-2"><ButtononClick={(e) => {e.stopPropagation();handleEditLocation(location);}}variant="ghost"size="sm"className="p-1 h-8 w-8"title="Edit location"><Edit className="h-4 w-4" /></Button><ButtononClick={(e) => {e.stopPropagation();handleDeleteLocation(location);}}variant="ghost"size="sm"className="p-1 h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"title="Delete location"><Trash2 className="h-4 w-4" /></Button></div></TableCell>)}