Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions IOT_service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
#FROM maven:3.9-eclipse-temurin-17 AS build
#WORKDIR /app
#COPY . .
#RUN
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
COPY IOT_service/pom.xml IOT_service/
COPY mainservice/pom.xml mainservice/
COPY apigateway/pom.xml apigateway/
COPY chatbotbackend/pom.xml chatbotbackend/
COPY discoveryserver/pom.xml discoveryserver/
COPY vitalReports-AI/pom.xml vitalReports-AI/
RUN mvn dependency:go-offline -pl IOT_service -am -q 2>/dev/null || true
COPY IOT_service/src IOT_service/src
RUN mvn -pl IOT_service -am clean package -DskipTests -q

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY IOT_service/target/*.jar app.jar
COPY --from=build /app/IOT_service/target/*.jar app.jar
EXPOSE 8082
ENTRYPOINT ["java","-jar","app.jar"]

41 changes: 13 additions & 28 deletions IOT_service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,56 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- H2 Database -->
<!-- ★ NEW: Security (required for SecurityConfig + JwtAuthFilter) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- PostgreSQL -->
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.7</version>
<scope>runtime</scope>
</dependency>

<!-- Lombok (only once!) -->
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- Kafka (kept but disabled via autoconfigure.exclude) -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

<!-- Eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- Test -->
<!-- Prometheus -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.IOT_service.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());

return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,126 +1,101 @@
package com.example.IOT_service.controller;

import com.example.IOT_service.model.Device;
import com.example.IOT_service.model.SensorData;
import com.example.IOT_service.service.DeviceService;
import com.example.IOT_service.service.SensorDataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.IOT_service.dto.DeviceAssignmentRequest;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/sensordata")
@CrossOrigin(origins = "*")
@RequiredArgsConstructor
@Slf4j
public class SensorDataController {

private final SensorDataService service;
private final SensorDataService sensorDataService;
private final DeviceService deviceService;

// POST endpoint - receives data from ESP32
@PostMapping
public ResponseEntity<?> receiveSensorData(@RequestBody SensorData data) {
try {
log.info("Received sensor data: RoomTemp={}, Humidity={}, WaterTemp={}, BPM={}, AvgBPM={}, SpO2={}",
data.getRoomTemp(), data.getHumidity(), data.getWaterTempC(),
data.getBpm(), data.getAvgBpm(), data.getSpo2());

SensorData saved = service.saveSensorData(data);

return ResponseEntity.ok(Map.of(
"success", true,
"message", "Data saved successfully",
"id", saved.getId(),
"timestamp", saved.getReceivedAt()
));

} catch (Exception e) {
log.error("Error saving sensor data", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of(
"success", false,
"message", "Error: " + e.getMessage()
));
}
}
// Device upload endpoint for hosted use
@PostMapping("/device/upload")
public ResponseEntity<?> receiveFromDevice(
@RequestHeader("X-Device-Id") String deviceId,
@RequestHeader("X-Device-Token") String deviceToken,
@RequestBody SensorData data) {

// GET all data
@GetMapping
public ResponseEntity<List<SensorData>> getAllData() {
return ResponseEntity.ok(service.getAllData());
}
log.info("Incoming device upload: deviceId={} bpm={} spo2={}", deviceId, data.getBpm(), data.getSpo2());

Device device = deviceService.validateDevice(deviceId, deviceToken);

data.setUserId(device.getUserId());
data.setDeviceId(device.getDeviceId());

// GET latest N records
@GetMapping("/latest/{limit}")
public ResponseEntity<List<SensorData>> getLatestData(@PathVariable int limit) {
return ResponseEntity.ok(service.getLatestData(limit));
SensorData saved = sensorDataService.save(data);

log.info("Saved: id={} userId={} deviceId={} bpm={} spo2={}",
saved.getId(),
saved.getUserId(),
saved.getDeviceId(),
saved.getBpm(),
saved.getSpo2());

return ResponseEntity.ok(Map.of(
"success", true,
"id", saved.getId(),
"userId", saved.getUserId(),
"deviceId", saved.getDeviceId()
));
}

// GET most recent single reading
@GetMapping("/current")
public ResponseEntity<?> getCurrentReading() {
return service.getLatestReading()
.map(data -> ResponseEntity.ok((Object) data))
.orElse(ResponseEntity.ok(Map.of("message", "No data available")));
// Optional localhost test endpoint - keep only during testing
@PostMapping
public ResponseEntity<?> receiveLocalTest(@RequestBody SensorData data) {
SensorData saved = sensorDataService.save(data);
return ResponseEntity.ok(Map.of("success", true, "id", saved.getId()));
}

// GET data from last N hours
@GetMapping("/recent/{hours}")
public ResponseEntity<List<SensorData>> getRecentData(@PathVariable int hours) {
return ResponseEntity.ok(service.getRecentData(hours));
@GetMapping("/patient/{userId}/latest/{limit}")
public ResponseEntity<List<SensorData>> getLatest(@PathVariable Long userId,
@PathVariable int limit) {
return ResponseEntity.ok(sensorDataService.getLatestForUser(userId, limit));
}

// DELETE old data
@DeleteMapping("/cleanup/{days}")
public ResponseEntity<?> cleanupOldData(@PathVariable int days) {
try {
service.deleteOldData(days);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "Deleted data older than " + days + " days"
));
} catch (Exception e) {
log.error("Error cleaning up data", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("success", false, "message", e.getMessage()));
}
@GetMapping("/patient/{userId}/current")
public ResponseEntity<?> getCurrent(@PathVariable Long userId) {
return sensorDataService.getLatestOne(userId)
.<ResponseEntity<?>>map(ResponseEntity::ok)
.orElse(ResponseEntity.ok(Map.of("message", "No data yet")));
}

// Health check
@GetMapping("/health")
public ResponseEntity<?> healthCheck() {
public ResponseEntity<?> health() {
return ResponseEntity.ok(Map.of(
"status", "healthy",
"timestamp", LocalDateTime.now(),
"service", "Sensor Data API"
"timestamp", LocalDateTime.now()
));
}

@GetMapping("/latest/{limit}/with-status")
public ResponseEntity<?> getLatestWithStatus(@PathVariable int limit) {
List<SensorData> data = service.getLatestData(limit);
if (data.isEmpty()) {
return ResponseEntity.ok(Map.of(
"data", List.of(),
"stale", true,
"ageSeconds", null,
"latestTimestamp", null
));
}

SensorData latest = data.get(0); // because repository returns DESC
long ageSeconds = java.time.Duration.between(latest.getReceivedAt(), LocalDateTime.now()).getSeconds();
boolean stale = ageSeconds > 30; // choose threshold
@PostMapping("/devices/assign")
public ResponseEntity<?> assignDevice(@RequestBody DeviceAssignmentRequest request) {
Device device = deviceService.assignDeviceToUser(request.getDeviceId(), request.getUserId());

return ResponseEntity.ok(Map.of(
"data", data,
"latestTimestamp", latest.getReceivedAt(),
"ageSeconds", ageSeconds,
"stale", stale
"success", true,
"message", "Device assigned successfully",
"deviceId", device.getDeviceId(),
"userId", device.getUserId()
));
}

}
@GetMapping("/devices")
public ResponseEntity<?> getAllDevices() {
return ResponseEntity.ok(deviceService.getAllDevices());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.IOT_service.dto;

import lombok.Data;

@Data
public class DeviceAssignmentRequest {
private String deviceId;
private Long userId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.IOT_service.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntime(RuntimeException ex) {
HttpStatus status = "Invalid or inactive device".equals(ex.getMessage())
? HttpStatus.UNAUTHORIZED
: HttpStatus.BAD_REQUEST;
return ResponseEntity.status(status).body(Map.of(
"error", ex.getMessage(),
"timestamp", LocalDateTime.now().toString()
));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAll(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"error", "Internal server error: " + ex.getMessage(),
"timestamp", LocalDateTime.now().toString()
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.IOT_service.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "devices")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Device {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "device_id", unique = true, nullable = false)
private String deviceId;

@Column(name = "device_token", nullable = false)
private String deviceToken;

@Column(name = "user_id", nullable = false)
private Long userId;

@Column(name = "status", nullable = false)
private String status; // ACTIVE / INACTIVE
}
Loading
Loading