Skip to content
Open
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
35 changes: 21 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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'
# Command to run the application
ENTRYPOINT ["java", "-jar", "UnravelDocs.jar"]
Original file line number Diff line number Diff line change
@@ -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<UnravelDocsDataResponse<String>> appStart() {

UnravelDocsDataResponse<String> response = new UnravelDocsDataResponse<>();
response.setStatusCode(HttpStatus.OK.value());
response.setStatus("success");
response.setMessage("Welcome to UnravelDocs API");
response.setData(null);

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
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
@Component
@RequiredArgsConstructor
public class UserRegisteredEventHandler implements EventHandler<UserRegisteredEvent> {

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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("*");
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class EventPublisherService {

public <T> void publishEvent(String exchange, String routingKey, BaseEvent<T> event) {
try {
/**
MessageProperties props = new MessageProperties();
props.setHeader("__TypeId__", event.getMetadata().getEventType());
props.setCorrelationId(event.getMetadata().getCorrelationId());
Expand All @@ -26,6 +27,21 @@ public <T> void publishEvent(String exchange, String routingKey, BaseEvent<T> 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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<OcrRequestedEvent> event) {
OcrRequestedEvent payload = event.getPayload();
Expand All @@ -26,6 +50,7 @@ public void receiveOcrRequestedEvent(BaseEvent<OcrRequestedEvent> event) {
s.sanitizeLogging(payload.getDocumentId()),
s.sanitizeLogging(correlationId));


try {
ocrService.processOcrRequest(payload.getCollectionId(), payload.getDocumentId());
} catch (Exception e) {
Expand All @@ -38,4 +63,5 @@ public void receiveOcrRequestedEvent(BaseEvent<OcrRequestedEvent> event) {
throw e;
}
}
}
*/
}
Loading