getHistory(String officerBadgeNo, Instant from, Instant to) {
+ return repo.findByOfficerBadgeNoAndTsBetweenOrderByTsAsc(officerBadgeNo, from, to);
+ }
+
+ @Override
+ public LocationPoint getLastLocation(String officerBadgeNo) {
+ var list = repo.findByOfficerBadgeNoOrderByTsDesc(officerBadgeNo, PageRequest.of(0, 1));
+ return list.isEmpty() ? null : list.get(0);
+ }
+}
diff --git a/src/main/java/com/crimeLink/analyzer/util/LogSanitizer.java b/src/main/java/com/crimeLink/analyzer/util/LogSanitizer.java
new file mode 100644
index 0000000..f5e39e2
--- /dev/null
+++ b/src/main/java/com/crimeLink/analyzer/util/LogSanitizer.java
@@ -0,0 +1,87 @@
+package com.crimeLink.analyzer.util;
+
+/**
+ * Utility class for sanitizing user-controlled input before logging.
+ *
+ * Prevents Log Injection (CWE-117) by stripping or escaping
+ * carriage-return, line-feed, and other ASCII control characters that an
+ * attacker could use to forge log entries.
+ *
+ * Usage:
+ *
+ * log.info("Processing file: {}", LogSanitizer.sanitize(file.getOriginalFilename()));
+ *
+ */
+public final class LogSanitizer {
+
+ /** Maximum length of a sanitized value written to a log line. */
+ private static final int MAX_LENGTH = 200;
+
+ private LogSanitizer() {
+ // utility class – no instances
+ }
+
+ /**
+ * Sanitize a {@code String} value so it is safe to include in a log message.
+ *
+ * - {@code null} → the literal string {@code "null"}
+ * - CR ({@code \r}) → the two-character escape {@code \r}
+ * - LF ({@code \n}) → the two-character escape {@code \n}
+ * - TAB ({@code \t}) → the two-character escape {@code \t}
+ * - Any other ASCII control character ({@code U+0000–U+001F}, {@code U+007F})
+ * except space → {@code ?}
+ * - Values longer than {@value #MAX_LENGTH} characters (after escaping)
+ * are truncated with a {@code …(truncated)} suffix
+ *
+ *
+ * @param input the raw, potentially attacker-controlled string
+ * @return a sanitized string safe for logging; never {@code null}
+ */
+ public static String sanitize(String input) {
+ if (input == null) {
+ return "null";
+ }
+
+ StringBuilder sb = new StringBuilder(Math.min(input.length(), MAX_LENGTH + 20));
+ for (int i = 0; i < input.length(); i++) {
+ char ch = input.charAt(i);
+ if (ch == '\r') {
+ sb.append("\\r");
+ } else if (ch == '\n') {
+ sb.append("\\n");
+ } else if (ch == '\t') {
+ sb.append("\\t");
+ } else if ((ch >= '\u0000' && ch <= '\u001F') || ch == '\u007F') {
+ sb.append('?');
+ } else {
+ sb.append(ch);
+ }
+
+ // Early exit when we already exceed the cap
+ if (sb.length() > MAX_LENGTH) {
+ break;
+ }
+ }
+
+ if (sb.length() > MAX_LENGTH) {
+ sb.setLength(MAX_LENGTH);
+ sb.append("…(truncated)");
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Convenience overload that accepts any {@link Object}.
+ * Calls {@link Object#toString()} before sanitizing.
+ *
+ * @param input the object whose string representation should be sanitized
+ * @return a sanitized string safe for logging; never {@code null}
+ */
+ public static String sanitize(Object input) {
+ if (input == null) {
+ return "null";
+ }
+ return sanitize(input.toString());
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8a3bce8..80d1aa1 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -38,3 +38,11 @@ jwt.refresh-expiration=${REFRESH_TOKEN_EXPIRATION:604800000}
python.call-analysis.url=${PYTHON_CALL_ANALYSIS_URL:http://localhost:5001}
python.facial-recognition.url=${PYTHON_FACIAL_RECOGNITION_URL:http://localhost:5002}
+#Supabase Configuration
+supabase.url=${SUPABASE_URL}
+supabase.service-key=${SUPABASE_SERVICE_KEY}
+
+# File Upload Configuration
+spring.servlet.multipart.enabled=true
+spring.servlet.multipart.max-file-size=10MB
+spring.servlet.multipart.max-request-size=50MB