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
25 changes: 21 additions & 4 deletions apigateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,33 @@ spring:

cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOriginPatterns:
- "http://localhost:5173"
- "http://localhost:3000"
- "https://*.azurecontainerapps.io"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
- PATCH
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
routes:
- id: iot-service
order: -10
uri: http://iot-service:8082
uri: lb://iot-service
predicates:
- Path=/api/sensordata/**
- Path=/api/iot/**
filters:
- StripPrefix=0
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

- id: chatbotbackend
uri: lb://chatbotbackend
Expand All @@ -38,7 +56,6 @@ spring:
- Path=/api/**
filters:
- StripPrefix=0
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

eureka:
client:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ services:
JWT_SECRET: ${JWT_SECRET}
EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: ${EUREKA_CLIENT_SERVICEURL_DEFAULTZONE}
SPRING_AUTOCONFIGURE_EXCLUDE: org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
VITALREPORTS_URL: http://vitalreports:8083

iot-service:
build:
Expand Down Expand Up @@ -134,6 +135,8 @@ services:
condition: service_healthy
environment:
EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://discoveryserver:8761/eureka
ECG_PYTHON_PATH: python
ECG_SCRIPT_PATH: ecg_predict.py

apigateway:
build:
Expand Down
18 changes: 18 additions & 0 deletions mainservice/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,22 @@


</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.example.mainservice.client;

import com.example.mainservice.dto.VitalAssessmentResponseDTO;
import com.example.mainservice.dto.VitalReadingRequestDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.http.HttpEntity;
import java.util.Map;

@Component
public class VitalReportsClient {

private static final Logger logger = LoggerFactory.getLogger(VitalReportsClient.class);

private final RestTemplate restTemplate = new RestTemplate();

@Value("${vitalreports.url:http://localhost:8081}")
private String vitalReportsUrl;

public VitalAssessmentResponseDTO evaluateVitals(VitalReadingRequestDTO request) {
String url = vitalReportsUrl + "/api/vital/vitals/evaluate";
try {
logger.info("Calling VitalReports-AI at {}", url);
ResponseEntity<VitalAssessmentResponseDTO> response =
restTemplate.postForEntity(url, request, VitalAssessmentResponseDTO.class);
return response.getBody();
} catch (Exception e) {
logger.error("Failed to evaluate vitals via VitalReports-AI: {}", e.getMessage());
VitalAssessmentResponseDTO fallback = new VitalAssessmentResponseDTO();
fallback.setTriageLevel("UNKNOWN");
fallback.setVitalStatus(new VitalAssessmentResponseDTO.VitalStatusDTO(
"UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"
));
return fallback;
}
}

public Map<String, Object> analyzeECG(String patientId, MultipartFile datFile, MultipartFile heaFile) {
String url = vitalReportsUrl + "/api/vital/ecg/analyze";
try {
logger.info("Sending ECG data to VitalReports-AI at {}", url);

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("patientId", patientId);
body.add("dat_file", datFile.getResource());
body.add("hea_file", heaFile.getResource());

HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);

@SuppressWarnings("unchecked")
ResponseEntity<Map> response = restTemplate.postForEntity(url, requestEntity, Map.class);
return response.getBody();
} catch (Exception e) {
logger.error("Failed to analyze ECG via VitalReports-AI: {}", e.getMessage());
throw new RuntimeException("ECG Analysis failed in VitalReports service: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@ public CorsConfigurationSource corsConfigurationSource() {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
// Use our corsConfigurationSource instead of disable() —
// this prevents mainservice from emitting its own CORS headers
// which would duplicate the ones the gateway already sets.
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

Expand All @@ -91,6 +88,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/api/auth/**", "/auth/**").permitAll()
.requestMatchers("/api/admin/**", "/admin/**").permitAll()
.requestMatchers("/api/doctor/**", "/doctor/**").permitAll()
.requestMatchers("/api/doctor-notes/**", "/doctor-notes/**").permitAll()
.requestMatchers("/api/patient/**", "/patient/**").permitAll()
.requestMatchers("/api/pendingdoctor/**", "/pendingdoctor/**").permitAll()
.requestMatchers("/api/dashboard/**", "/dashboard/**").permitAll()
.requestMatchers("/api/vital/**", "/vital/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/payments/pay/**", "/payments/pay/**").permitAll()
.requestMatchers("/api/payments/notify", "/payments/notify").permitAll()
.requestMatchers("/ws/**", "/ws").permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,23 @@ public List<AppointmentDTO> getAllAppointments() {

System.out.println("PAID APPOINTMENTS = " + list.size());

return list.stream().map(a -> new AppointmentDTO(
a.getId(),
a.getAvailability() != null ? a.getAvailability().getId() : null,
a.getDoctor() != null ? a.getDoctor().getName() : "N/A",
a.getDoctor() != null ? a.getDoctor().getSpecialty() : "N/A",
a.getDoctor() != null ? a.getDoctor().getConsultationFee() : 0.0,
a.getAppointmentType().getTypeName(),
a.getPhysicalLocation() != null ? a.getPhysicalLocation() : a.getOnlineLink(),
a.getBookingDate(),
a.getBookingTime(),
a.getReason(),
a.getPaymentStatus().name(),
a.getAppointmentStatus().name()
)).toList();
return list.stream().map(a -> {
AppointmentDTO dto = new AppointmentDTO();
dto.setAppointmentId(a.getId());
dto.setAvailabilityId(a.getAvailability() != null ? a.getAvailability().getId() : null);
dto.setDoctorName(a.getDoctor() != null ? a.getDoctor().getName() : "N/A");
dto.setSpecialty(a.getDoctor() != null ? a.getDoctor().getSpecialty() : "N/A");
dto.setConsultationFee(a.getDoctor() != null ? a.getDoctor().getConsultationFee() : 0.0);
dto.setAppointmentType(a.getAppointmentType().getTypeName());
dto.setLocationOrLink(a.getPhysicalLocation() != null ? a.getPhysicalLocation() : a.getOnlineLink());
dto.setBookingDate(a.getBookingDate());
dto.setBookingTime(a.getBookingTime());
dto.setReason(a.getReason());
dto.setPaymentStatus(a.getPaymentStatus().name());
dto.setAppointmentStatus(a.getAppointmentStatus().name());
dto.setPatientName(a.getPatientName());
return dto;
}).toList();
}

// 🔹 CONFIRM APPOINTMENT
Expand All @@ -67,4 +70,4 @@ public ResponseEntity<String> confirmAppointment(

return ResponseEntity.ok("Confirmed");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.List;
import java.util.Map;

@CrossOrigin(origins = "http://localhost:5173")
@RestController
@RequestMapping("/api/appointments")
@RequiredArgsConstructor
Expand All @@ -19,9 +19,10 @@ public class AppointmentController {
private final AppointmentService appointmentService;

@PostMapping("/book")
public ResponseEntity<?> bookAppointment(@RequestBody AppointmentRequestDTO dto) {
public ResponseEntity<?> bookAppointment(@RequestBody AppointmentRequestDTO dto, Principal principal) {
try {
return ResponseEntity.ok(appointmentService.bookAppointment(dto));
String patientName = principal != null ? principal.getName() : "Unknown";
return ResponseEntity.ok(appointmentService.bookAppointment(dto, patientName));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example.mainservice.controller;

import com.example.mainservice.dto.AppointmentDTO;
import com.example.mainservice.entity.Appointment;
import com.example.mainservice.entity.SpecialDoctor;
import com.example.mainservice.entity.enums.AppointmentStatus;
import com.example.mainservice.repository.AppointmentRepository;
import com.example.mainservice.repository.SpecialDoctorRepository;
import com.example.mainservice.service.AppointmentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/doctor/appointments")
@RequiredArgsConstructor
public class DoctorAppointmentController {

private final AppointmentService appointmentService;
private final SpecialDoctorRepository specialDoctorRepository;
private final AppointmentRepository appointmentRepository;

@GetMapping("/{doctorId}")
public ResponseEntity<List<AppointmentDTO>> getDoctorAppointments(@PathVariable Long doctorId) {
return ResponseEntity.ok(appointmentService.getDoctorAppointments(doctorId));
}

@GetMapping("/by-email")
public ResponseEntity<?> getDoctorAppointmentsByEmail(@RequestParam String email) {
SpecialDoctor doctor = specialDoctorRepository.findByEmail(email).orElse(null);
if (doctor == null) {
return ResponseEntity.ok(List.of());
}
return ResponseEntity.ok(appointmentService.getDoctorAppointments(doctor.getId()));
}

@PostMapping("/confirm/{id}")
public ResponseEntity<String> confirmAppointment(
@PathVariable Long id,
@RequestParam(required = false) String physicalLocation,
@RequestParam(required = false) String zoomLink
) {
Appointment a = appointmentRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Appointment not found"));

if ("Physical".equalsIgnoreCase(a.getAppointmentType().getTypeName())) {
a.setPhysicalLocation(physicalLocation);
} else {
a.setOnlineLink(zoomLink);
}

a.setAppointmentStatus(AppointmentStatus.CONFIRMED);
appointmentRepository.save(a);

return ResponseEntity.ok("Confirmed");
}
}
Loading
Loading