{"record":{"id":"79352206b12f6b24","repo":"Freika/dawarich","slug":"errordata-error-failed-to-isedit-update","errorCode":null,"errorMessage":"errorData.error || `Failed to ${isEdit ? \"update\" : \"create\"} visit`","messagePattern":"errorData\\.error \\|\\| `Failed to (.+?) visit`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/javascript/controllers/visit_creation_v2_controller.js","lineNumber":179,"sourceCode":"      const url = isEdit\n        ? `/api/v1/visits/${this.editingVisitId}`\n        : \"/api/v1/visits\"\n      const method = isEdit ? \"PATCH\" : \"POST\"\n\n      const response = await fetch(url, {\n        method: method,\n        headers: {\n          \"Content-Type\": \"application/json\",\n          Authorization: `Bearer ${this.apiKeyValue}`,\n          \"X-CSRF-Token\":\n            document.querySelector('meta[name=\"csrf-token\"]')?.content || \"\",\n        },\n        body: JSON.stringify(visitData),\n      })\n\n      if (!response.ok) {\n        const errorData = await response.json()\n        throw new Error(\n          errorData.error || `Failed to ${isEdit ? \"update\" : \"create\"} visit`,\n        )\n      }\n\n      const visit = await response.json()\n\n      // Show success message\n      this.showToast(\n        `Visit ${isEdit ? \"updated\" : \"created\"} successfully`,\n        \"success\",\n      )\n\n      // Close modal\n      this.close()\n\n      // Dispatch event to notify map controller\n      const eventName = isEdit ? \"visit:updated\" : \"visit:created\"\n      document.dispatchEvent(","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/javascript/controllers/visit_creation_v2_controller.js#L161-L197","documentation":"Thrown by the Stimulus visit-creation controller when the POST/PATCH to the visits API returns a non-2xx response. The Rails endpoint (Api::V1::VisitsController#create/update) answers 422 Unprocessable Content with a JSON body like {error: 'Failed to create visit: ...'} when Visits::Create fails (duplicate visit, invalid place, ActiveRecord::RecordInvalid) and 401 when the Bearer key/CSRF is bad. The fallback template string 'Failed to create/update visit' only surfaces when the body is JSON but has no 'error' key.","triggerScenarios":"POST /api/v1/visits or PATCH /api/v1/visits/:id where Visits::Create rescues ActiveRecord::RecordNotUnique ('Failed to create visit: duplicate visit'), Place.create! fails RecordInvalid (bad/blank latitude, longitude, name), started_at/ended_at unparseable so Time.zone.parse returns nil and duration computation fails, or the API key meta value is empty/expired producing a 401 whose body still parses as JSON without an 'error' field.","commonSituations":"Double-submitting the visit form (creates a duplicate visit within the 100m ST_DWithin match), expired or rotated api_key, CSRF meta tag missing on a cached page, empty date inputs, reverse proxy returning a JSON-ish 502 body, editing a visit whose place_id/area_id belongs to another user (invalid_place/invalid_area 422 messages).","solutions":["Open the browser Network tab and read the 422 body's error field - it carries the exact server message (e.g. 'Failed to create visit: Validation failed: Name can't be blank')","Ensure the payload includes non-blank name, valid latitude/longitude, and parseable started_at/ended_at before submitting","Verify this.apiKeyValue is a valid API key and the csrf-token meta tag exists on the page","Disable the submit button while the request is in flight to avoid duplicate-visit RecordNotUnique","Guard response.json() with a content-type check so HTML error pages (401/500) surface a readable message instead of a SyntaxError"],"exampleFix":"// before\nif (!response.ok) {\n  const errorData = await response.json()\n  throw new Error(errorData.error || `Failed to ${isEdit ? \"update\" : \"create\"} visit`)\n}\n\n// after\nif (!response.ok) {\n  const errorData = await response.json().catch(() => ({}))\n  throw new Error(\n    errorData.error ||\n      `Failed to ${isEdit ? \"update\" : \"create\"} visit (HTTP ${response.status})`,\n  )\n}","handlingStrategy":"try-catch","validationCode":"const required = ['name', 'latitude', 'longitude', 'started_at', 'ended_at']\nconst missing = required.filter((k) => !visitData[k] && visitData[k] !== 0)\nif (missing.length) {\n  this.showToast(`Missing: ${missing.join(', ')}`, 'error')\n  return\n}\nif (!this.apiKeyValue) {\n  this.showToast('API key missing', 'error')\n  return\n}","typeGuard":null,"tryCatchPattern":"try {\n  const response = await fetch(url, opts)\n  if (!response.ok) {\n    const errorData = await response.json().catch(() => ({}))\n    throw new Error(errorData.error || `HTTP ${response.status}`)\n  }\n  // ...\n} catch (error) {\n  this.showToast(error.message, 'error')\n} finally {\n  submitButton.disabled = false\n}","preventionTips":["Disable the submit button while the request is in flight to avoid duplicate-visit 422s","Parse error bodies with response.json().catch(() => ({})) so HTML error pages do not mask the real status","Include response.status in fallback messages because server error text is not always present"],"tags":["stimulus","fetch","api","visits","validation"],"backgroundTag":"api-request-failed","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}