diff --git a/Dockerfile b/Dockerfile index 3b9528e..5bf7f6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,36 @@ -# Build stage +# Use a base image with Java 21 installed FROM openjdk:21-jdk-slim AS build + +# Set the working directory inside the container WORKDIR /app + +# Copy the Maven wrapper and the project definition file COPY .mvn/ .mvn COPY mvnw pom.xml ./ + +# Make the Maven wrapper executable RUN chmod +x mvnw + +# Download dependencies. This layer is cached if pom.xml doesn't change. RUN ./mvnw dependency:go-offline + +# Copy the rest of your application's source code COPY src ./src + +# Package the application into a JAR file RUN ./mvnw package -DskipTests && ls -la /app/target/ -# Runtime stage +# Use a smaller base image for the final application FROM openjdk:21-jdk-slim -WORKDIR /app -# Install Tesseract OCR and language data -RUN apt-get update && apt-get install -y --no-install-recommends \ - tesseract-ocr tesseract-ocr-eng \ - && rm -rf /var/lib/apt/lists/* +# Set the working directory inside the container +WORKDIR /app -# Copy app +# Copy the Maven wrapper and the project definition file from the build stage COPY --from=build /app/target/*.jar UnravelDocs.jar -# Tesseract data path -ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/4.00/tessdata -# Keep JVM memory within dyno limits -ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:+UseSerialGC" +# Expose the port your application runs on +EXPOSE 8080 -# Heroku provides PORT; bind server to it -CMD sh -c 'java $JAVA_OPTS -Dserver.port=${PORT:-8080} -Dspring.profiles.active=heroku -jar UnravelDocs.jar' \ No newline at end of file +# Command to run the application +ENTRYPOINT ["java", "-jar", "UnravelDocs.jar"] \ No newline at end of file diff --git a/src/main/java/com/extractor/unraveldocs/appstart/AppStartController.java b/src/main/java/com/extractor/unraveldocs/appstart/AppStartController.java new file mode 100644 index 0000000..c1a17c1 --- /dev/null +++ b/src/main/java/com/extractor/unraveldocs/appstart/AppStartController.java @@ -0,0 +1,31 @@ +package com.extractor.unraveldocs.appstart; + +import com.extractor.unraveldocs.shared.response.UnravelDocsDataResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/") +@Tag(name = "Unraveldocs API Start App", description = "The root for starting unraveldocs") +public class AppStartController { + @Operation( + summary = "App Starting Point", + description = "Starts the unraveldocs application" + ) + @GetMapping + public ResponseEntity> appStart() { + + UnravelDocsDataResponse response = new UnravelDocsDataResponse<>(); + response.setStatusCode(HttpStatus.OK.value()); + response.setStatus("success"); + response.setMessage("Welcome to UnravelDocs API"); + response.setData(null); + + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/extractor/unraveldocs/auth/components/UserRegisteredEventHandler.java b/src/main/java/com/extractor/unraveldocs/auth/components/UserRegisteredEventHandler.java index d1ae076..6047670 100644 --- a/src/main/java/com/extractor/unraveldocs/auth/components/UserRegisteredEventHandler.java +++ b/src/main/java/com/extractor/unraveldocs/auth/components/UserRegisteredEventHandler.java @@ -1,11 +1,15 @@ package com.extractor.unraveldocs.auth.components; import com.extractor.unraveldocs.auth.events.UserRegisteredEvent; +import com.extractor.unraveldocs.config.EmailRabbitMQConfig; import com.extractor.unraveldocs.documents.utils.SanitizeLogging; import com.extractor.unraveldocs.events.EventHandler; +import com.extractor.unraveldocs.messaging.dto.EmailMessage; import com.extractor.unraveldocs.messaging.emailtemplates.AuthEmailTemplateService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; @Slf4j @@ -13,21 +17,29 @@ @RequiredArgsConstructor public class UserRegisteredEventHandler implements EventHandler { - private final SanitizeLogging sanitizeLogging; private final AuthEmailTemplateService authEmailTemplateService; + private final RabbitTemplate rabbitTemplate; + private final SanitizeLogging sanitizeLogging; + @Override public void handleEvent(UserRegisteredEvent event) { log.info("Processing UserRegisteredEvent for email: {}", sanitizeLogging.sanitizeLogging(event.getEmail())); try { - authEmailTemplateService.sendVerificationEmail( + EmailMessage emailMessage = authEmailTemplateService.prepareVerificationEmail( event.getEmail(), event.getFirstName(), event.getLastName(), event.getVerificationToken(), event.getExpiration() ); + + rabbitTemplate.convertAndSend( + EmailRabbitMQConfig.EXCHANGE_NAME, + EmailRabbitMQConfig.ROUTING_KEY, + emailMessage + ); log.info("Sent verification email to: {}", sanitizeLogging.sanitizeLogging(event.getEmail())); } catch (Exception e) { log.error("Failed to send verification email to {}: {}", sanitizeLogging.sanitizeLogging(event.getEmail()), e.getMessage(), e); diff --git a/src/main/java/com/extractor/unraveldocs/config/RabbitMQConfig.java b/src/main/java/com/extractor/unraveldocs/config/RabbitMQConfig.java index bd3933d..233d1ed 100644 --- a/src/main/java/com/extractor/unraveldocs/config/RabbitMQConfig.java +++ b/src/main/java/com/extractor/unraveldocs/config/RabbitMQConfig.java @@ -2,6 +2,7 @@ import com.extractor.unraveldocs.auth.events.UserRegisteredEvent; import com.extractor.unraveldocs.auth.events.WelcomeEvent; +import com.extractor.unraveldocs.ocrprocessing.events.OcrRequestedEvent; import com.extractor.unraveldocs.user.events.*; import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.core.*; @@ -157,6 +158,7 @@ public DefaultClassMapper classMapper() { idClassMapping.put("PasswordResetRequested", PasswordResetEvent.class); idClassMapping.put("PasswordResetSuccessful", PasswordResetSuccessfulEvent.class); idClassMapping.put("WelcomeEvent", WelcomeEvent.class); + idClassMapping.put("OcrRequested", OcrRequestedEvent.class); classMapper.setIdClassMapping(idClassMapping); classMapper.setTrustedPackages("*"); diff --git a/src/main/java/com/extractor/unraveldocs/events/EmailQueueConsumer.java b/src/main/java/com/extractor/unraveldocs/events/EmailQueueConsumer.java new file mode 100644 index 0000000..40d2900 --- /dev/null +++ b/src/main/java/com/extractor/unraveldocs/events/EmailQueueConsumer.java @@ -0,0 +1,28 @@ +package com.extractor.unraveldocs.events; + +import com.extractor.unraveldocs.config.EmailRabbitMQConfig; +import com.extractor.unraveldocs.messaging.dto.EmailMessage; +import com.extractor.unraveldocs.messaging.emailservice.EmailOrchestratorService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class EmailQueueConsumer { + private final EmailOrchestratorService emailOrchestratorService; + + @RabbitListener(queues = EmailRabbitMQConfig.QUEUE_NAME) + public void handleEmailRequest(EmailMessage emailMessage) { + log.info("Received email request for: {}", emailMessage.getTo()); + try { + emailOrchestratorService.sendEmail(emailMessage); + log.info("Email sent to: {}", emailMessage.getTo()); + } catch (Exception e) { + log.error("Failed to send email to {}: {}", emailMessage.getTo(), e.getMessage(), e); + throw e; // Rethrow to trigger retry or dead-lettering + } + } +} diff --git a/src/main/java/com/extractor/unraveldocs/events/EventPublisherService.java b/src/main/java/com/extractor/unraveldocs/events/EventPublisherService.java index bf21784..8bf594d 100644 --- a/src/main/java/com/extractor/unraveldocs/events/EventPublisherService.java +++ b/src/main/java/com/extractor/unraveldocs/events/EventPublisherService.java @@ -17,6 +17,7 @@ public class EventPublisherService { public void publishEvent(String exchange, String routingKey, BaseEvent event) { try { + /** MessageProperties props = new MessageProperties(); props.setHeader("__TypeId__", event.getMetadata().getEventType()); props.setCorrelationId(event.getMetadata().getCorrelationId()); @@ -26,6 +27,21 @@ public void publishEvent(String exchange, String routingKey, BaseEvent ev rabbitTemplate.send(exchange, routingKey, message); + log.info("Published event of type '{}' with correlationId '{}' to exchange '{}' with routing key '{}'", + event.getMetadata().getEventType(), + event.getMetadata().getCorrelationId(), + exchange, + routingKey); + **/ + + Object payload = event.getPayload(); + + rabbitTemplate.convertAndSend(exchange, routingKey, payload, message -> { + MessageProperties props = message.getMessageProperties(); + props.setHeader("__TypeId__", event.getMetadata().getEventType()); + props.setCorrelationId(event.getMetadata().getCorrelationId()); + return message; + }); log.info("Published event of type '{}' with correlationId '{}' to exchange '{}' with routing key '{}'", event.getMetadata().getEventType(), event.getMetadata().getCorrelationId(), diff --git a/src/main/java/com/extractor/unraveldocs/messaging/emailtemplates/AuthEmailTemplateService.java b/src/main/java/com/extractor/unraveldocs/messaging/emailtemplates/AuthEmailTemplateService.java index c980684..668710b 100644 --- a/src/main/java/com/extractor/unraveldocs/messaging/emailtemplates/AuthEmailTemplateService.java +++ b/src/main/java/com/extractor/unraveldocs/messaging/emailtemplates/AuthEmailTemplateService.java @@ -17,10 +17,10 @@ public class AuthEmailTemplateService { @Value("${app.base.url}") private String baseUrl; - public void sendVerificationEmail(String email, String firstName, String lastName, String token, String expiration) { + public EmailMessage prepareVerificationEmail(String email, String firstName, String lastName, String token, String expiration) { String verificationUrl = baseUrl + "/api/v1/auth/verify-email?token=" + token; - EmailMessage message = EmailMessage.builder() + return EmailMessage.builder() .to(email) .subject("Email Verification Token") .templateName("emailVerificationToken") @@ -31,7 +31,10 @@ public void sendVerificationEmail(String email, String firstName, String lastNam "expiration", expiration )) .build(); + } - emailOrchestratorService.sendEmail(message); + public void sendVerificationEmail(String email, String firstName, String lastName, String token, String expiration) { + EmailMessage emailMessage = prepareVerificationEmail(email, firstName, lastName, token, expiration); + emailOrchestratorService.sendEmail(emailMessage); } } \ No newline at end of file diff --git a/src/main/java/com/extractor/unraveldocs/ocrprocessing/events/OcrMessageListener.java b/src/main/java/com/extractor/unraveldocs/ocrprocessing/events/OcrMessageListener.java index d86f727..219e6ba 100644 --- a/src/main/java/com/extractor/unraveldocs/ocrprocessing/events/OcrMessageListener.java +++ b/src/main/java/com/extractor/unraveldocs/ocrprocessing/events/OcrMessageListener.java @@ -7,6 +7,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; @Slf4j @@ -16,6 +18,28 @@ public class OcrMessageListener { private final ProcessOcrService ocrService; private final SanitizeLogging s; + @RabbitListener(queues = RabbitMQConfig.OCR_EVENTS_QUEUE) + public void receiveOcrRequestedEvent( + OcrRequestedEvent payload, + @Header(AmqpHeaders.CORRELATION_ID) String correlationId) { + log.info("Received OCR request for collection ID: {}, document ID: {}. CorrelationId: {}", + s.sanitizeLogging(payload.getCollectionId()), + s.sanitizeLogging(payload.getDocumentId()), + s.sanitizeLogging(correlationId)); + try { + ocrService.processOcrRequest(payload.getCollectionId(), payload.getDocumentId()); + } catch (Exception e) { + log.error("Error processing OCR request for collection ID: {}, document ID: {}. CorrelationId: {}. Error: {}", + s.sanitizeLogging(payload.getCollectionId()), + s.sanitizeLogging(payload.getDocumentId()), + s.sanitizeLogging(correlationId), + e.getMessage(), e); + // Re-throw the exception to trigger the retry/DLQ mechanism + throw e; + } + } + + /* @RabbitListener(queues = RabbitMQConfig.OCR_EVENTS_QUEUE) public void receiveOcrRequestedEvent(BaseEvent event) { OcrRequestedEvent payload = event.getPayload(); @@ -26,6 +50,7 @@ public void receiveOcrRequestedEvent(BaseEvent event) { s.sanitizeLogging(payload.getDocumentId()), s.sanitizeLogging(correlationId)); + try { ocrService.processOcrRequest(payload.getCollectionId(), payload.getDocumentId()); } catch (Exception e) { @@ -38,4 +63,5 @@ public void receiveOcrRequestedEvent(BaseEvent event) { throw e; } } -} \ No newline at end of file + */ +}