{"record":{"id":"b4da2a7a96d5a2a3","repo":"Freika/dawarich","slug":"no-user-found","errorCode":null,"errorMessage":"No user found","messagePattern":"No user found","errorType":"http","errorClass":"Maps::BoundsCalculator::NoUserFoundError","httpStatus":404,"severity":"error","filePath":"app/services/maps/bounds_calculator.rb","lineNumber":31,"sourceCode":"\n    def call\n      validate_inputs!\n\n      start_timestamp = parse_date_parameter(@start_date)\n      end_timestamp = parse_date_parameter(@end_date)\n\n      bounds_result = execute_bounds_query(start_timestamp, end_timestamp)\n      point_count = bounds_result['point_count'].to_i\n\n      return build_no_data_response if point_count.zero?\n\n      build_success_response(bounds_result, point_count)\n    end\n\n    private\n\n    def validate_inputs!\n      raise NoUserFoundError, I18n.t('services.maps.bounds_calculator.no_user') unless @user\n      raise NoDateRangeError, I18n.t('services.maps.bounds_calculator.no_date_range') unless @start_date && @end_date\n    end\n\n    def execute_bounds_query(start_timestamp, end_timestamp)\n      ActiveRecord::Base.connection.exec_query(\n        \"SELECT COUNT(*) as point_count,\n                ST_YMin(ST_Extent(lonlat::geometry)) as min_lat,\n                ST_YMax(ST_Extent(lonlat::geometry)) as max_lat,\n                ST_XMin(ST_Extent(lonlat::geometry)) as min_lng,\n                ST_XMax(ST_Extent(lonlat::geometry)) as max_lng\n         FROM points\n         WHERE user_id = $1\n         AND timestamp BETWEEN $2 AND $3\",\n        'bounds_query',\n        [@user.id, start_timestamp, end_timestamp]\n      ).first\n    end\n","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/services/maps/bounds_calculator.rb#L13-L49","documentation":"Maps::BoundsCalculator::NoUserFoundError raised in validate_inputs! when the user: keyword argument is nil. The service immediately runs a raw SQL bounds query against points scoped to @user.id, so a nil user is rejected up front with 'No user found' instead of producing a broken query or another user's bounds.","triggerScenarios":"Instantiating Maps::BoundsCalculator.new(user: nil, start_date:, end_date:) - e.g. an API key that no longer maps to an existing user, current_user evaluating to nil in an unauthenticated controller path, or the user record being destroyed between authentication and the service call (background jobs holding a stale id).","commonSituations":"Deleted or archived accounts with still-valid sessions/API keys, jobs resolving a user id that was later destroyed, or a controller missing its authentication before_action.","solutions":["Verify the user object is loaded and persisted before calling the service","If triggered via API key, confirm the key still belongs to an existing user","Re-authenticate / refresh the session and retry","Guard the caller: skip or 401 when the user cannot be resolved instead of passing nil through"],"exampleFix":"# before\ncalculator = Maps::BoundsCalculator.new(user: current_user, ...)  # current_user can be nil\n\n# after\nreturn head :unauthorized if current_user.nil?\ncalculator = Maps::BoundsCalculator.new(user: current_user, ...)","handlingStrategy":"type-guard","validationCode":"raise ArgumentError, 'user required' unless user.is_a?(User) && user.persisted?","typeGuard":"def boundable_user?(candidate)\n  candidate.is_a?(User) && candidate.persisted? && !candidate.destroyed?\nend","tryCatchPattern":"begin\n  Maps::BoundsCalculator.new(user: user, start_date: s, end_date: e).call\nrescue Maps::BoundsCalculator::NoUserFoundError\n  head :unauthorized\nend","preventionTips":["Resolve and persist-check the user in the controller before building the service","In jobs, re-find the user and no-op if gone: User.exists?(user_id) or return","Don't cache user objects across requests"],"tags":["maps","validation","nil-user","bounds"],"backgroundTag":"user-not-found","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}