Repository: uclapi/uclapi

Issue:
Workspaces Endpoint for Live Maps throws an error 500 if an incorrect survey_id / map_id combo are given
As explained in the title.

This should instead yield a proper 400 error as we do for other endpoints.


Discussion:

Resolving patch:
diff --git a/backend/uclapi/workspaces/occupeye/cache.py b/backend/uclapi/workspaces/occupeye/cache.py
--- a/backend/uclapi/workspaces/occupeye/cache.py
+++ b/backend/uclapi/workspaces/occupeye/cache.py
@@ -52,13 +52,19 @@ def cache_maps_for_survey(self, survey_id):
         survey_maps_list_key = (
             self._const.SURVEY_MAPS_LIST_KEY
         ).format(survey_id)
-        pipeline = self._redis.pipeline()
         survey_maps_data = authenticated_request(
             self._const.URL_MAPS_BY_SURVEY.format(survey_id),
             self.bearer_token
         )
+        pipeline = self._redis.pipeline()
+
+        self.delete_maps(
+            pipeline,
+            survey_id,
+            survey_maps_list_key,
+            survey_maps_data
+        )
 
-        pipeline.delete(survey_maps_list_key)
         for survey_map in survey_maps_data:
             survey_map_id = self._const.SURVEY_MAP_DATA_KEY.format(
                 survey_id,
@@ -76,6 +82,7 @@ def cache_maps_for_survey(self, survey_id):
                 survey_maps_list_key,
                 survey_map["MapID"]
             )
+
         pipeline.execute()
 
     def cache_survey_data(self):
@@ -85,12 +92,11 @@ def cache_survey_data(self):
         helper function above to tie all maps to surveys.
         """
         pipeline = self._redis.pipeline()
-        pipeline.delete(self._const.SURVEYS_LIST_KEY)
         surveys_data = authenticated_request(
             self._const.URL_SURVEYS,
             self.bearer_token
         )
-
+        self.delete_surveys(pipeline, surveys_data)
         for survey in surveys_data:
             survey_id = survey["SurveyID"]
             survey_key = self._const.SURVEY_DATA_KEY.format(
@@ -189,8 +195,13 @@ def cache_survey_sensor_data(self, survey_id):
         survey_sensors_list_key = (
             self._const.SURVEY_SENSORS_LIST_KEY
         ).format(survey_id)
+        self.delete_sensors(
+            pipeline,
+            survey_id,
+            survey_sensors_list_key,
+            all_sensors_data
+        )
 
-        pipeline.delete(survey_sensors_list_key)
         for sensor_data in all_sensors_data:
             hardware_id = sensor_data["HardwareID"]
             sensor_data_key = (
@@ -584,3 +595,124 @@ def feed_cache(self, full):
         self._redis.set(last_modified_key, current_timestamp)
 
         print("[+] Done")
+
+    def delete_maps(
+        self, pipeline, survey_id,
+        survey_maps_list_key, survey_maps_data
+    ):
+        """Delete maps that no longer exist in a survey"""
+        redis_survey_maps_set = set(self._redis.lrange(
+            survey_maps_list_key,
+            0,
+            self._redis.llen(survey_maps_list_key)
+        ))
+        api_survey_maps_id_set = set(
+            [survey_map["MapID"] for survey_map in survey_maps_data]
+        )
+        maps_to_delete = redis_survey_maps_set - api_survey_maps_id_set
+
+        pipeline.delete(survey_maps_list_key)
+        for map_id in maps_to_delete:
+            pipeline.delete(
+                self._const.SURVEY_MAP_DATA_KEY.format(survey_id, map_id)
+            )
+            pipeline.delete(
+                self._const.SURVEY_MAP_VMAX_X_KEY.format(survey_id, map_id)
+            )
+            pipeline.delete(
+                self._const.SURVEY_MAP_VMAX_Y_KEY.format(survey_id, map_id)
+            )
+            pipeline.delete(
+                self._const.SURVEY_MAP_VIEWBOX_KEY.format(survey_id, map_id)
+            )
+            survey_maps_sensors_key = (
+                self._const.SURVEY_MAP_SENSORS_LIST_KEY.format(
+                    survey_id,
+                    map_id
+                )
+            )
+            survey_maps_sensors_list = self._redis.lrange(
+                survey_maps_sensors_key,
+                0,
+                self._redis.llen(survey_maps_sensors_key)
+            )
+            for sensor_id in survey_maps_sensors_list:
+                pipeline.delete(
+                    self._const.SURVEY_MAP_SENSOR_PROPERTIES_KEY.format(
+                        survey_id,
+                        map_id,
+                        sensor_id
+                    )
+                )
+            image_id = self._redis.hgetall(
+                self._const.SURVEY_MAP_DATA_KEY.format(survey_id, map_id)
+            )['image_id']
+            self.delete_image(pipeline, image_id)
+
+            pipeline.delete(survey_maps_sensors_key)
+
+    def delete_surveys(self, pipeline, surveys_data):
+        """Delete surveys that no longer exist"""
+        redis_survey_set = set(self._redis.lrange(
+            self._const.SURVEYS_LIST_KEY,
+            0,
+            self._redis.llen(self._const.SURVEYS_LIST_KEY)
+        ))
+        api_survey_id_set = set(
+            [survey["SurveyID"] for survey in surveys_data]
+        )
+        surveys_to_delete = redis_survey_set - api_survey_id_set
+        pipeline.delete(self._const.SURVEYS_LIST_KEY)
+        for survey_id in surveys_to_delete:
+            pipeline.delete(self._const.SURVEY_DATA_KEY.format(survey_id))
+            pipeline.delete(self._const.SUMMARY_CACHE_SURVEY.format(survey_id))
+            pipeline.delete(
+                self._const.SURVEY_MAX_TIMESTAMP_KEY.format(survey_id)
+            )
+
+            survey_maps_list_key = self._const.SURVEY_MAPS_LIST_KEY.format(
+                survey_id
+            )
+            self.delete_maps(pipeline, survey_id, survey_maps_list_key, [])
+
+            survey_sensors_list_key = (
+                self._const.SURVEY_SENSORS_LIST_KEY.format(survey_id)
+            )
+            self.delete_sensors(
+                pipeline,
+                survey_id,
+                survey_sensors_list_key,
+                []
+            )
+
+    def delete_sensors(
+        self, pipeline, survey_id,
+        survey_sensors_list_key, all_sensors_data
+    ):
+        """Delete sensors that no longer exist in a survey"""
+        redis_survey_sensors_set = set(self._redis.lrange(
+            survey_sensors_list_key,
+            0,
+            self._redis.llen(survey_sensors_list_key)
+        ))
+        api_survey_sensors_id_set = set(
+            [survey_sensor["HardwareID"] for survey_sensor in all_sensors_data]
+        )
+        sensors_to_delete = (
+            redis_survey_sensors_set - api_survey_sensors_id_set
+        )
+        pipeline.delete(survey_sensors_list_key)
+        for sensor_id in sensors_to_delete:
+            pipeline.delete(
+                self._const.SURVEY_SENSOR_DATA_KEY.format(survey_id, sensor_id)
+            )
+            pipeline.delete(
+                self._const.SURVEY_SENSOR_STATUS_KEY.format(
+                    survey_id, sensor_id
+                )
+            )
+
+    def delete_image(self, pipeline, image_id):
+        "Deletes images that no longer are used by the API"""
+        pipeline.delete(self._const.IMAGE_BASE64_KEY.format(image_id))
+        pipeline.delete(self._const.IMAGE_CONTENT_TYPE_KEY.format(image_id))


Test patch: