Skip to content

Commit 8bc5461

Browse files
authored
Merge pull request #36 from arosha-w/sidebar-adjustments
Sidebar adjustments
2 parents 57f891e + 562ed0a commit 8bc5461

9 files changed

Lines changed: 729 additions & 7 deletions

File tree

src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,15 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
5151
.requestMatchers("/api/facial/health").permitAll() // ML service health check
5252
.requestMatchers("/api/call-analysis/health").permitAll() // ML service health check
5353

54-
// ML Service endpoints - Investigator role only
54+
// ML Service endpoints
5555
.requestMatchers("/api/call-analysis/**").hasRole("Investigator")
56+
.requestMatchers("/api/facial/register").hasAnyRole("Investigator", "OIC")
57+
.requestMatchers("/api/facial/criminals").hasAnyRole("Investigator", "OIC")
5658
.requestMatchers("/api/facial/**").hasRole("Investigator")
59+
60+
// Criminal CRUD (direct DB, no Python)
61+
.requestMatchers("/api/criminals/**").hasAnyRole("Investigator", "OIC")
62+
.requestMatchers("/api/criminals").hasAnyRole("Investigator", "OIC")
5763

5864
.requestMatchers("/api/database/**").permitAll()
5965
.requestMatchers("/api/test").permitAll()
@@ -79,8 +85,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
7985
.requestMatchers("/api/weapon/**").hasRole("OIC")
8086
.requestMatchers("/api/weapon-issue/**").hasRole("OIC")
8187

82-
// Admin/OIC routes (officer data, locations, users)
83-
.requestMatchers("/api/users/field-officers").hasAnyRole("Admin", "OIC")
88+
// Admin/OIC/Investigator routes (officer data, locations, users)
89+
.requestMatchers("/api/users/field-officers").hasAnyRole("Admin", "OIC", "Investigator")
90+
.requestMatchers("/api/admin/officers/*/locations/**").hasAnyRole("Admin", "OIC", "Investigator")
8491
.requestMatchers("/api/admin/**").hasAnyRole("OIC", "Admin")
8592

8693
.anyRequest().authenticated())
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package com.crimeLink.analyzer.controller;
2+
3+
import com.crimeLink.analyzer.service.CriminalService;
4+
import com.crimeLink.analyzer.util.LogSanitizer;
5+
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
import org.springframework.web.multipart.MultipartFile;
10+
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Optional;
14+
15+
/**
16+
* REST Controller for Criminal record management (CRUD).
17+
* Operates directly against the database via JPA — coordinates with Python ML for embeddings.
18+
*
19+
* Endpoints:
20+
* - POST /api/criminals - Create new criminal (with photo upload + embedding)
21+
* - GET /api/criminals - List all criminals
22+
* - GET /api/criminals/{id} - Get criminal details
23+
* - PUT /api/criminals/{id} - Update criminal profile (with optional photo)
24+
* - DELETE /api/criminals/{id} - Delete criminal record
25+
*/
26+
@RestController
27+
@RequestMapping("/api/criminals")
28+
@RequiredArgsConstructor
29+
@Slf4j
30+
public class CriminalController {
31+
32+
private final CriminalService criminalService;
33+
34+
/**
35+
* Create a new criminal record with optional photo.
36+
*/
37+
@PostMapping
38+
public ResponseEntity<?> createCriminal(
39+
@RequestParam("name") String name,
40+
@RequestParam("nic") String nic,
41+
@RequestParam(value = "photo", required = false) MultipartFile photo,
42+
@RequestParam(value = "risk_level", required = false) String riskLevel,
43+
@RequestParam(value = "crime_history", required = false) String crimeHistory,
44+
@RequestParam(value = "address", required = false) String address,
45+
@RequestParam(value = "contact_number", required = false) String contactNumber,
46+
@RequestParam(value = "secondary_contact", required = false) String secondaryContact,
47+
@RequestParam(value = "date_of_birth", required = false) String dateOfBirth,
48+
@RequestParam(value = "gender", required = false) String gender,
49+
@RequestParam(value = "alias", required = false) String alias,
50+
@RequestParam(value = "status", required = false) String status) {
51+
52+
try {
53+
log.info("Criminal registration requested: name={}, nic={}",
54+
LogSanitizer.sanitize(name), LogSanitizer.sanitize(nic));
55+
56+
if (name == null || name.trim().isEmpty()) {
57+
return ResponseEntity.badRequest().body(Map.of("error", "Name is required"));
58+
}
59+
if (nic == null || nic.trim().isEmpty()) {
60+
return ResponseEntity.badRequest().body(Map.of("error", "NIC is required"));
61+
}
62+
63+
Map<String, Object> result = criminalService.createCriminal(
64+
name.trim(), nic.trim(), riskLevel, crimeHistory,
65+
address, contactNumber, secondaryContact,
66+
dateOfBirth, gender, alias, status, photo);
67+
68+
return ResponseEntity.ok(result);
69+
70+
} catch (Exception e) {
71+
log.error("Criminal registration failed: {}", e.getMessage());
72+
return ResponseEntity.internalServerError()
73+
.body(Map.of("error", "Registration failed: " + e.getMessage()));
74+
}
75+
}
76+
77+
/**
78+
* Get all criminals (summary list).
79+
*/
80+
@GetMapping
81+
public ResponseEntity<?> getAllCriminals() {
82+
try {
83+
List<Map<String, Object>> criminals = criminalService.getAllCriminals();
84+
return ResponseEntity.ok(criminals);
85+
} catch (Exception e) {
86+
log.error("Failed to fetch criminals: {}", e.getMessage());
87+
return ResponseEntity.internalServerError()
88+
.body(Map.of("error", "Failed to fetch criminals: " + e.getMessage()));
89+
}
90+
}
91+
92+
/**
93+
* Get full details for a specific criminal.
94+
*/
95+
@GetMapping("/{criminalId}")
96+
public ResponseEntity<?> getCriminalDetails(@PathVariable String criminalId) {
97+
try {
98+
Optional<Map<String, Object>> result = criminalService.getCriminalDetails(criminalId);
99+
if (result.isEmpty()) {
100+
return ResponseEntity.status(404)
101+
.body(Map.of("error", "Criminal not found: " + LogSanitizer.sanitize(criminalId)));
102+
}
103+
return ResponseEntity.ok(result.get());
104+
} catch (Exception e) {
105+
log.error("Failed to fetch criminal {}: {}", LogSanitizer.sanitize(criminalId), e.getMessage());
106+
return ResponseEntity.internalServerError()
107+
.body(Map.of("error", "Failed to fetch criminal details: " + e.getMessage()));
108+
}
109+
}
110+
111+
/**
112+
* Update an existing criminal's profile data (with optional photo change).
113+
*/
114+
@PutMapping("/{criminalId}")
115+
public ResponseEntity<?> updateCriminal(
116+
@PathVariable String criminalId,
117+
@RequestParam(value = "name", required = false) String name,
118+
@RequestParam(value = "nic", required = false) String nic,
119+
@RequestParam(value = "photo", required = false) MultipartFile photo,
120+
@RequestParam(value = "risk_level", required = false) String riskLevel,
121+
@RequestParam(value = "crime_history", required = false) String crimeHistory,
122+
@RequestParam(value = "address", required = false) String address,
123+
@RequestParam(value = "contact_number", required = false) String contactNumber,
124+
@RequestParam(value = "secondary_contact", required = false) String secondaryContact,
125+
@RequestParam(value = "date_of_birth", required = false) String dateOfBirth,
126+
@RequestParam(value = "gender", required = false) String gender,
127+
@RequestParam(value = "alias", required = false) String alias,
128+
@RequestParam(value = "status", required = false) String status) {
129+
130+
try {
131+
log.info("Criminal update requested for ID: {}", LogSanitizer.sanitize(criminalId));
132+
133+
Optional<Map<String, Object>> result = criminalService.updateCriminal(
134+
criminalId, name, nic, riskLevel, crimeHistory,
135+
address, contactNumber, secondaryContact,
136+
dateOfBirth, gender, alias, status, photo);
137+
138+
if (result.isEmpty()) {
139+
return ResponseEntity.status(404)
140+
.body(Map.of("error", "Criminal not found: " + LogSanitizer.sanitize(criminalId)));
141+
}
142+
143+
Map<String, Object> response = new java.util.LinkedHashMap<>();
144+
response.put("message", "Criminal updated successfully");
145+
response.put("criminal", result.get());
146+
return ResponseEntity.ok(response);
147+
148+
} catch (Exception e) {
149+
log.error("Criminal update failed for {}: {}", LogSanitizer.sanitize(criminalId), e.getMessage());
150+
return ResponseEntity.internalServerError()
151+
.body(Map.of("error", "Update failed: " + e.getMessage()));
152+
}
153+
}
154+
155+
/**
156+
* Delete a criminal record (cascades to suspect_photos, cleans up storage).
157+
*/
158+
@DeleteMapping("/{criminalId}")
159+
public ResponseEntity<?> deleteCriminal(@PathVariable String criminalId) {
160+
try {
161+
log.info("Criminal deletion requested for ID: {}", LogSanitizer.sanitize(criminalId));
162+
163+
boolean deleted = criminalService.deleteCriminal(criminalId);
164+
if (!deleted) {
165+
return ResponseEntity.status(404)
166+
.body(Map.of("error", "Criminal not found: " + LogSanitizer.sanitize(criminalId)));
167+
}
168+
169+
return ResponseEntity.ok(Map.of("message", "Criminal deleted successfully", "criminal_id", criminalId));
170+
171+
} catch (Exception e) {
172+
log.error("Criminal deletion failed for {}: {}", LogSanitizer.sanitize(criminalId), e.getMessage());
173+
return ResponseEntity.internalServerError()
174+
.body(Map.of("error", "Deletion failed: " + e.getMessage()));
175+
}
176+
}
177+
}

src/main/java/com/crimeLink/analyzer/controller/FacialRecognitionController.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,15 @@ public ResponseEntity<?> registerCriminal(
9898
@RequestParam(value = "criminal_id", required = false) String criminalId,
9999
@RequestParam("name") String name,
100100
@RequestParam("nic") String nic,
101-
@RequestParam(value = "risk_level", required = false, defaultValue = "medium") String riskLevel) {
101+
@RequestParam(value = "risk_level", required = false, defaultValue = "medium") String riskLevel,
102+
@RequestParam(value = "crime_history", required = false) String crimeHistory,
103+
@RequestParam(value = "address", required = false) String address,
104+
@RequestParam(value = "contact_number", required = false) String contactNumber,
105+
@RequestParam(value = "secondary_contact", required = false) String secondaryContact,
106+
@RequestParam(value = "date_of_birth", required = false) String dateOfBirth,
107+
@RequestParam(value = "gender", required = false) String gender,
108+
@RequestParam(value = "alias", required = false) String alias,
109+
@RequestParam(value = "status", required = false, defaultValue = "active") String status) {
102110

103111
try {
104112
log.info("Criminal registration requested: {} ({})", LogSanitizer.sanitize(name), LogSanitizer.sanitize(nic));
@@ -122,7 +130,8 @@ public ResponseEntity<?> registerCriminal(
122130

123131
// Forward to ML service
124132
JsonNode result = facialRecognitionService.registerCriminal(
125-
photo, criminalId, name, nic, riskLevel);
133+
photo, criminalId, name, nic, riskLevel, crimeHistory,
134+
address, contactNumber, secondaryContact, dateOfBirth, gender, alias, status);
126135

127136
return ResponseEntity.ok(result);
128137

src/main/java/com/crimeLink/analyzer/entity/Criminal.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.crimeLink.analyzer.entity;
22

33
import jakarta.persistence.*;
4+
import java.time.LocalDate;
45

56
@Entity
67
@Table(name = "criminals")
@@ -28,6 +29,24 @@ public class Criminal {
2829
@Column(name = "status", length = 50)
2930
private String status;
3031

32+
@Column(name = "risk_level", length = 50)
33+
private String riskLevel;
34+
35+
@Column(name = "crime_history", columnDefinition = "TEXT")
36+
private String crimeHistory;
37+
38+
@Column(name = "primary_photo_url", length = 500)
39+
private String primaryPhotoUrl;
40+
41+
@Column(name = "date_of_birth")
42+
private LocalDate dateOfBirth;
43+
44+
@Column(name = "gender", length = 10)
45+
private String gender;
46+
47+
@Column(name = "alias", length = 255)
48+
private String alias;
49+
3150
// Constructors
3251
public Criminal() {}
3352

@@ -87,4 +106,52 @@ public String getStatus() {
87106
public void setStatus(String status) {
88107
this.status = status;
89108
}
109+
110+
public String getRiskLevel() {
111+
return riskLevel;
112+
}
113+
114+
public void setRiskLevel(String riskLevel) {
115+
this.riskLevel = riskLevel;
116+
}
117+
118+
public String getCrimeHistory() {
119+
return crimeHistory;
120+
}
121+
122+
public void setCrimeHistory(String crimeHistory) {
123+
this.crimeHistory = crimeHistory;
124+
}
125+
126+
public String getPrimaryPhotoUrl() {
127+
return primaryPhotoUrl;
128+
}
129+
130+
public void setPrimaryPhotoUrl(String primaryPhotoUrl) {
131+
this.primaryPhotoUrl = primaryPhotoUrl;
132+
}
133+
134+
public LocalDate getDateOfBirth() {
135+
return dateOfBirth;
136+
}
137+
138+
public void setDateOfBirth(LocalDate dateOfBirth) {
139+
this.dateOfBirth = dateOfBirth;
140+
}
141+
142+
public String getGender() {
143+
return gender;
144+
}
145+
146+
public void setGender(String gender) {
147+
this.gender = gender;
148+
}
149+
150+
public String getAlias() {
151+
return alias;
152+
}
153+
154+
public void setAlias(String alias) {
155+
this.alias = alias;
156+
}
90157
}

src/main/java/com/crimeLink/analyzer/repository/CriminalRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.data.repository.query.Param;
77
import org.springframework.stereotype.Repository;
88

9+
import java.util.List;
910
import java.util.Optional;
1011

1112
@Repository
@@ -17,4 +18,7 @@ public interface CriminalRepository extends JpaRepository<Criminal, String> {
1718

1819
@Query("SELECT c FROM Criminal c WHERE c.contactNumber = :phone OR c.secondaryContact = :phone")
1920
Optional<Criminal> findByPhoneNumber(@Param("phone") String phone);
21+
22+
@Query(value = "SELECT id FROM criminals WHERE face_embedding IS NOT NULL", nativeQuery = true)
23+
List<String> findIdsWithEmbedding();
2024
}

0 commit comments

Comments
 (0)