From 7145e7e2c72773e7d1956e354122005f352cec61 Mon Sep 17 00:00:00 2001 From: jo-elimu <1451036+jo-elimu@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:05:57 +0700 Subject: [PATCH 1/3] feat(rest): video learning events #1894 --- .../VideoLearningEventsRestController.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java diff --git a/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java new file mode 100644 index 000000000..2b07a9eb7 --- /dev/null +++ b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java @@ -0,0 +1,73 @@ +package ai.elimu.rest.v2.analytics; + +import ai.elimu.model.v2.enums.Language; +import ai.elimu.util.AnalyticsHelper; +import ai.elimu.util.ConfigHelper; +import java.io.File; +import javax.servlet.http.HttpServletResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping(value = "/rest/v2/analytics/video-learning-events", produces = MediaType.APPLICATION_JSON_VALUE) +public class VideoLearningEventsRestController { + + private Logger logger = LogManager.getLogger(); + + @RequestMapping(value = "/csv", method = RequestMethod.POST) + public String handleUploadCsvRequest( + @RequestParam("file") MultipartFile multipartFile, + HttpServletResponse response + ) { + logger.info("handleUploadCsvRequest"); + + JSONObject jsonResponseObject = new JSONObject(); + try { + // Expected format: "7161a85a0e4751cd_3001012_video-learning-events_2020-04-23.csv" + String originalFilename = multipartFile.getOriginalFilename(); + logger.info("originalFilename: " + originalFilename); + + String androidIdExtractedFromFilename = AnalyticsHelper.extractAndroidIdFromCsvFilename(originalFilename); + logger.info("androidIdExtractedFromFilename: \"" + androidIdExtractedFromFilename + "\""); + + Integer versionCodeExtractedFromFilename = AnalyticsHelper.extractVersionCodeFromCsvFilename(originalFilename); + logger.info("versionCodeExtractedFromFilename: " + versionCodeExtractedFromFilename); + + byte[] bytes = multipartFile.getBytes(); + logger.info("bytes.length: " + bytes.length); + + // Store the original CSV file on the filesystem + File elimuAiDir = new File(System.getProperty("user.home"), ".elimu-ai"); + File languageDir = new File(elimuAiDir, "lang-" + Language.valueOf(ConfigHelper.getProperty("content.language"))); + File analyticsDir = new File(languageDir, "analytics"); + File androidIdDir = new File(analyticsDir, "android-id-" + androidIdExtractedFromFilename); + File versionCodeDir = new File(androidIdDir, "version-code-" + versionCodeExtractedFromFilename); + File videoLearningEventsDir = new File(versionCodeDir, "video-learning-events"); + videoLearningEventsDir.mkdirs(); + File csvFile = new File(videoLearningEventsDir, originalFilename); + logger.info("Storing CSV file at " + csvFile); + multipartFile.transferTo(csvFile); + } catch (Exception ex) { + logger.error(ex); + + jsonResponseObject.put("result", "error"); + jsonResponseObject.put("errorMessage", ex.getMessage()); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + jsonResponseObject.put("result", "success"); + jsonResponseObject.put("successMessage", "The CSV file was uploaded"); + response.setStatus(HttpStatus.OK.value()); + + String jsonResponse = jsonResponseObject.toString(); + logger.info("jsonResponse: " + jsonResponse); + return jsonResponse; + } +} From 797519463d010b6d2561b2d6140dc00ac7e6acca Mon Sep 17 00:00:00 2001 From: jo-elimu <1451036+jo-elimu@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:58:05 +0700 Subject: [PATCH 2/3] fix: logical error in the success response (#1894) #1894 --- .../v2/analytics/VideoLearningEventsRestController.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java index 2b07a9eb7..43be26444 100644 --- a/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java +++ b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java @@ -55,6 +55,10 @@ public String handleUploadCsvRequest( File csvFile = new File(videoLearningEventsDir, originalFilename); logger.info("Storing CSV file at " + csvFile); multipartFile.transferTo(csvFile); + + jsonResponseObject.put("result", "success"); + jsonResponseObject.put("successMessage", "The CSV file was uploaded"); + response.setStatus(HttpStatus.OK.value()); } catch (Exception ex) { logger.error(ex); @@ -62,9 +66,6 @@ public String handleUploadCsvRequest( jsonResponseObject.put("errorMessage", ex.getMessage()); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); } - jsonResponseObject.put("result", "success"); - jsonResponseObject.put("successMessage", "The CSV file was uploaded"); - response.setStatus(HttpStatus.OK.value()); String jsonResponse = jsonResponseObject.toString(); logger.info("jsonResponse: " + jsonResponse); From bea16994df915f26a6e81bcb844b21be94a682bf Mon Sep 17 00:00:00 2001 From: jo-elimu <1451036+jo-elimu@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:47:56 +0700 Subject: [PATCH 3/3] test(rest): video learning events #1894 --- .../VideoLearningEventsRestController.java | 12 ++++ ...VideoLearningEventsRestControllerTest.java | 58 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/test/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestControllerTest.java diff --git a/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java index 43be26444..553d58db8 100644 --- a/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java +++ b/src/main/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestController.java @@ -31,9 +31,21 @@ public String handleUploadCsvRequest( JSONObject jsonResponseObject = new JSONObject(); try { + String contentType = multipartFile.getContentType(); + logger.info("contentType: " + contentType); + + long size = multipartFile.getSize(); + logger.info("size: " + size); + if (size == 0) { + throw new IllegalArgumentException("Empty file"); + } + // Expected format: "7161a85a0e4751cd_3001012_video-learning-events_2020-04-23.csv" String originalFilename = multipartFile.getOriginalFilename(); logger.info("originalFilename: " + originalFilename); + if (originalFilename.length() != 61) { + throw new IllegalArgumentException("Unexpected filename"); + } String androidIdExtractedFromFilename = AnalyticsHelper.extractAndroidIdFromCsvFilename(originalFilename); logger.info("androidIdExtractedFromFilename: \"" + androidIdExtractedFromFilename + "\""); diff --git a/src/test/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestControllerTest.java b/src/test/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestControllerTest.java new file mode 100644 index 000000000..2285a7b03 --- /dev/null +++ b/src/test/java/ai/elimu/rest/v2/analytics/VideoLearningEventsRestControllerTest.java @@ -0,0 +1,58 @@ +package ai.elimu.rest.v2.analytics; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +public class VideoLearningEventsRestControllerTest { + + @InjectMocks + private VideoLearningEventsRestController videoLearningEventsRestController; + + @Mock + private HttpServletResponse response; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testHandleUploadCsvRequest_invalidFilename() { + MultipartFile multipartFile = new MockMultipartFile( + "file", + "invalid_filename.csv", + "text/csv", + "test content".getBytes() + ); + String jsonResponse = videoLearningEventsRestController.handleUploadCsvRequest(multipartFile, response); + JSONObject jsonObject = new JSONObject(jsonResponse); + assertEquals("error", jsonObject.getString("result")); + assertEquals("Unexpected filename", jsonObject.getString("errorMessage")); + verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + @Test + public void testHandleUploadCsvRequest_emptyFile() { + MultipartFile multipartFile = new MockMultipartFile( + "file", + "7161a85a0e4751cd_3001012_video-learning-events_2020-04-23.csv", + "text/csv", + "".getBytes() + ); + String jsonResponse = videoLearningEventsRestController.handleUploadCsvRequest(multipartFile, response); + JSONObject jsonObject = new JSONObject(jsonResponse); + assertEquals("error", jsonObject.getString("result")); + assertEquals("Empty file", jsonObject.getString("errorMessage")); + verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } +}