diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c6243b2..25f7381 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -37,6 +37,11 @@ jobs:
aws ecr describe-repositories --repository-names ${{ vars.EMAIL_REQUEST_PROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.EMAIL_REQUEST_PROCESSOR_ECR_NAME }}
aws ecr describe-repositories --repository-names ${{ vars.EMAIL_REQUEST_PREPROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.EMAIL_REQUEST_PREPROCESSOR_ECR_NAME }}
aws ecr describe-repositories --repository-names ${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }}
+ aws ecr describe-repositories --repository-names ${{ vars.ATTACHMENT_SAVER_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.ATTACHMENT_SAVER_ECR_NAME }}
+ aws ecr describe-repositories --repository-names ${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }}
+ aws ecr describe-repositories --repository-names ${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }}
+ aws ecr describe-repositories --repository-names ${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }}
+ aws ecr describe-repositories --repository-names ${{ vars.RESUME_ECR_NAME }} || aws ecr create-repository --repository-name ${{ vars.RESUME_ECR_NAME }}
- name: Generate timestamp
id: timestamp
@@ -64,6 +69,16 @@ jobs:
docker push $ECR_REGISTRY/${{ vars.EMAIL_REQUEST_PREPROCESSOR_ECR_NAME }}:$IMAGE_TAG
docker build -t $ECR_REGISTRY/${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }}:$IMAGE_TAG ./lambdas/EmailProcessor/EmailResponseProcessorFunction
docker push $ECR_REGISTRY/${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }}:$IMAGE_TAG
+ docker build -t $ECR_REGISTRY/${{ vars.ATTACHMENT_SAVER_ECR_NAME }}:$IMAGE_TAG ./lambdas/AttachmentSaver/AttachmentSaverFunction
+ docker push $ECR_REGISTRY/${{ vars.ATTACHMENT_SAVER_ECR_NAME }}:$IMAGE_TAG
+ docker build -t $ECR_REGISTRY/${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }}:$IMAGE_TAG ./lambdas/TranscriptionProcessor/TranscriptionFunction
+ docker push $ECR_REGISTRY/${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }}:$IMAGE_TAG
+ docker build -t $ECR_REGISTRY/${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }}:$IMAGE_TAG ./lambdas/ResumeProcessor/ResumeRequestProcessorFunction
+ docker push $ECR_REGISTRY/${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }}:$IMAGE_TAG
+ docker build -t $ECR_REGISTRY/${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }}:$IMAGE_TAG ./lambdas/ResumeProcessor/ResumeRequestPreProcessorFunction
+ docker push $ECR_REGISTRY/${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }}:$IMAGE_TAG
+ docker build -t $ECR_REGISTRY/${{ vars.RESUME_ECR_NAME }}:$IMAGE_TAG ./lambdas/ResumeProcessor/ResumeFunction
+ docker push $ECR_REGISTRY/${{ vars.RESUME_ECR_NAME }}:$IMAGE_TAG
terraform_plan:
if: github.event_name == 'pull_request'
@@ -112,6 +127,11 @@ jobs:
-var="email_response_processor_lambda_repository_name=${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }}" \
-var="sender_email=${{ vars.SENDER_EMAIL }}" \
-var="chat_rule_recipient=${{ vars.CHAT_RULE_RECIPIENT }}" \
+ -var="attachment_saver_lambda_repository_name=${{ vars.ATTACHMENT_SAVER_ECR_NAME }}" \
+ -var="transcription_processor_lambda_repository_name=${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }}" \
+ -var="resume_request_processor_lambda_repository_name=${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }}" \
+ -var="resume_request_preprocessor_lambda_repository_name=${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }}" \
+ -var="resume_lambda_repository_name=${{ vars.RESUME_ECR_NAME }}" \
-no-color -out=terraform.tfplan
- name: Upload Terraform Plan
@@ -200,6 +220,11 @@ jobs:
for digest in $(aws ecr list-images --repository-name ${{ vars.EMAIL_REQUEST_PROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.EMAIL_REQUEST_PROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
for digest in $(aws ecr list-images --repository-name ${{ vars.EMAIL_REQUEST_PREPROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.EMAIL_REQUEST_PREPROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
for digest in $(aws ecr list-images --repository-name ${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
+ for digest in $(aws ecr list-images --repository-name ${{ vars.ATTACHMENT_SAVER_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.ATTACHMENT_SAVER_ECR_NAME }} --image-ids imageDigest=$digest; done
+ for digest in $(aws ecr list-images --repository-name ${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
+ for digest in $(aws ecr list-images --repository-name ${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
+ for digest in $(aws ecr list-images --repository-name ${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }} --image-ids imageDigest=$digest; done
+ for digest in $(aws ecr list-images --repository-name ${{ vars.RESUME_ECR_NAME }} --query 'sort_by(imageIds,&imagePushedAt)[0:-2].imageDigest' --output text); do aws ecr batch-delete-image --repository-name ${{ vars.RESUME_ECR_NAME }} --image-ids imageDigest=$digest; done
deploy-infra:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@@ -236,6 +261,11 @@ jobs:
-var="email_response_processor_lambda_repository_name=${{ vars.EMAIL_RESPONSE_PROCESSOR_ECR_NAME }}" \
-var="sender_email=${{ vars.SENDER_EMAIL }}" \
-var="chat_rule_recipient=${{ vars.CHAT_RULE_RECIPIENT }}" \
+ -var="attachment_saver_lambda_repository_name=${{ vars.ATTACHMENT_SAVER_ECR_NAME }}" \
+ -var="transcription_processor_lambda_repository_name=${{ vars.TRANSCRIPTION_PROCESSOR_ECR_NAME }}" \
+ -var="resume_request_processor_lambda_repository_name=${{ vars.RESUME_REQUEST_PROCESSOR_ECR_NAME }}" \
+ -var="resume_request_preprocessor_lambda_repository_name=${{ vars.RESUME_REQUEST_PREPROCESSOR_ECR_NAME }}" \
+ -var="resume_lambda_repository_name=${{ vars.RESUME_ECR_NAME }}" \
-auto-approve -no-color -input=false
diff --git a/lambdas/AttachmentSaver/AttachmentSaver.iml b/lambdas/AttachmentSaver/AttachmentSaver.iml
new file mode 100644
index 0000000..482334b
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaver.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/Dockerfile b/lambdas/AttachmentSaver/AttachmentSaverFunction/Dockerfile
new file mode 100644
index 0000000..73a72f7
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/Dockerfile
@@ -0,0 +1,15 @@
+FROM public.ecr.aws/sam/build-java17:latest as build-image
+
+WORKDIR "/task"
+COPY src/ src/
+COPY pom.xml ./
+
+RUN mvn -q clean install
+RUN mvn dependency:copy-dependencies -DincludeScope=compile
+
+FROM public.ecr.aws/lambda/java:17
+
+COPY --from=build-image /task/target/classes /var/task/
+COPY --from=build-image /task/target/dependency /var/task/lib
+# Command can be overwritten by providing a different command in the template directly.
+CMD ["com.levio.awsdemo.attachmentsaver.App::handleRequest"]
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/dependency-reduced-pom.xml b/lambdas/AttachmentSaver/AttachmentSaverFunction/dependency-reduced-pom.xml
new file mode 100644
index 0000000..33b6018
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/dependency-reduced-pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+ com.levio.aws-demo
+ attachment-saver
+ Attachment Saver
+ 1.0
+
+
+
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+
+ 17
+ 17
+
+
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/events/event.json b/lambdas/AttachmentSaver/AttachmentSaverFunction/events/event.json
new file mode 100644
index 0000000..995c40b
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/events/event.json
@@ -0,0 +1,38 @@
+{
+ "Records": [
+ {
+ "eventVersion": "2.0",
+ "eventSource": "aws:s3",
+ "awsRegion": "us-east-1",
+ "eventTime": "1970-01-01T00:00:00.000Z",
+ "eventName": "ObjectCreated:Put",
+ "userIdentity": {
+ "principalId": "EXAMPLE"
+ },
+ "requestParameters": {
+ "sourceIPAddress": "127.0.0.1"
+ },
+ "responseElements": {
+ "x-amz-request-id": "EXAMPLE123456789",
+ "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
+ },
+ "s3": {
+ "s3SchemaVersion": "1.0",
+ "configurationId": "testConfigRule",
+ "bucket": {
+ "name": "levio-demo-fev-esta-ses-bucket-dev",
+ "ownerIdentity": {
+ "principalId": "EXAMPLE"
+ },
+ "arn": "arn:aws:s3:::example-bucket"
+ },
+ "object": {
+ "key": "resume/email/u987v5umtglfcd0gm92traijlp7vqq44g8uat401",
+ "size": 1024,
+ "eTag": "0123456789abcdef0123456789abcdef",
+ "sequencer": "0A1B2C3D4E5F678901"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/lambda.tf b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/lambda.tf
new file mode 100644
index 0000000..4fa2488
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/lambda.tf
@@ -0,0 +1,71 @@
+data "aws_ecr_image" "lambda_image" {
+ repository_name = var.lambda_repository_name
+ most_recent = true
+}
+
+data "aws_caller_identity" "current" {}
+
+module "lambda_function_container_image" {
+ timeout = 60
+ source = "terraform-aws-modules/lambda/aws"
+ function_name = var.lambda_function_name
+ create_package = false
+ image_uri = data.aws_ecr_image.lambda_image.image_uri
+ package_type = "Image"
+ memory_size = 1024
+ role_name = "${var.lambda_function_name}-role"
+ attach_policy_statements = true
+
+ environment_variables = {
+ }
+
+ policy_statements = {
+ log_group = {
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogGroup"
+ ]
+ resources = [
+ "arn:aws:logs:*:*:*"
+ ]
+ }
+
+ log_write = {
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ]
+ resources = [
+ "arn:aws:logs:*:*:log-group:/aws/${var.lambda_function_name}/*:*"
+ ]
+ }
+
+ s3 = {
+ effect = "Allow"
+ actions = [
+ "s3:Get*",
+ "s3:List*",
+ "s3:Describe*",
+ "s3:PutObject",
+ "s3-object-lambda:Get*",
+ "s3-object-lambda:List*",
+ "s3-object-lambda:WriteGetObjectResponse"
+ ]
+ resources = [
+ var.ses_bucket_arn,
+ "${var.ses_bucket_arn}/*"
+ ]
+ }
+ }
+
+ create_current_version_allowed_triggers = false
+
+ allowed_triggers = {
+ s3 = {
+ principal = "s3.amazonaws.com"
+ source_arn = var.ses_bucket_arn
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/output.tf b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/output.tf
new file mode 100644
index 0000000..9e15297
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/output.tf
@@ -0,0 +1,3 @@
+output "lambda_function_arn" {
+ value = module.lambda_function_container_image.lambda_function_arn
+}
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/variables.tf b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/variables.tf
new file mode 100644
index 0000000..06e90d8
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/iac/variables.tf
@@ -0,0 +1,24 @@
+variable "lambda_function_name" {
+ type = string
+ nullable = false
+}
+
+variable "lambda_repository_name" {
+ type = string
+ nullable = false
+}
+
+variable "ses_bucket_name" {
+ type = string
+ nullable = false
+}
+
+variable "ses_bucket_arn" {
+ type = string
+ nullable = false
+}
+
+variable "aws_region" {
+ type = string
+ default = "us-east-1"
+}
\ No newline at end of file
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/pom.xml b/lambdas/AttachmentSaver/AttachmentSaverFunction/pom.xml
new file mode 100644
index 0000000..174cdec
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/pom.xml
@@ -0,0 +1,82 @@
+
+ 4.0.0
+ com.levio.aws-demo
+ attachment-saver
+ 1.0
+ jar
+ Attachment Saver
+
+ 17
+ 17
+
+
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.2.1
+
+
+ com.amazonaws
+ aws-lambda-java-events
+ 3.11.0
+
+
+ software.amazon.awssdk
+ s3
+ 2.20.52
+
+
+ software.amazon.awssdk
+ apache-client
+ 2.20.52
+
+
+ com.sun.mail
+ jakarta.mail
+ 2.0.1
+
+
+ jakarta.mail
+ jakarta.mail-api
+ 2.1.2
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.3
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-joda
+ 2.15.3
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/App.java b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/App.java
new file mode 100644
index 0000000..aa6aed0
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/App.java
@@ -0,0 +1,55 @@
+package com.levio.awsdemo.attachmentsaver;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.levio.awsdemo.attachmentsaver.service.S3Service;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Part;
+
+import java.io.IOException;
+import java.util.List;
+
+public class App implements RequestHandler {
+
+ private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JodaModule());
+
+ private final S3Service s3Service;
+
+ public App() {
+ this.s3Service = new S3Service();
+ }
+
+ public App(S3Service s3Service) {
+ this.s3Service = s3Service;
+ }
+
+ public Void handleRequest(final S3EventNotification input, final Context context) {
+ try {
+ String json = objectMapper.writeValueAsString(input);
+ System.out.println(json);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+
+ input.getRecords().forEach(s3EventNotificationRecord -> {
+ try {
+ List attachments = s3Service.getAttachments(s3EventNotificationRecord.getS3());
+ attachments.forEach(part -> {
+ try {
+ s3Service.saveAttachment(part, s3EventNotificationRecord.getS3());
+ } catch (MessagingException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (MessagingException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ return null;
+ }
+}
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/service/S3Service.java b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/service/S3Service.java
new file mode 100644
index 0000000..6c5d926
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/service/S3Service.java
@@ -0,0 +1,85 @@
+package com.levio.awsdemo.attachmentsaver.service;
+
+import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
+import com.levio.awsdemo.attachmentsaver.util.EmailUtils;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Part;
+import jakarta.mail.Session;
+import jakarta.mail.internet.MimeMessage;
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectResponse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+public class S3Service {
+
+ private final S3Client s3 = S3Client.builder()
+ .region(Region.US_EAST_1)
+ .build();
+
+ public List getAttachments(S3EventNotification.S3Entity s3Entity) throws MessagingException, IOException {
+ GetObjectRequest objectRequest = GetObjectRequest
+ .builder()
+ .key(s3Entity.getObject().getKey())
+ .bucket(s3Entity.getBucket().getName())
+ .build();
+
+ ResponseBytes objectBytes = s3.getObjectAsBytes(objectRequest);
+ System.out.println(new String(objectBytes.asByteArray()));
+ InputStream inputStream = objectBytes.asInputStream();
+
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage message = new MimeMessage(session, inputStream);
+
+ return EmailUtils.extractAttachments(message);
+ }
+
+ public void saveAttachment(Part part, S3EventNotification.S3Entity s3Entity) throws MessagingException, IOException {
+ try (InputStream inputStream = part.getInputStream()) {
+ String key = getKey(part, s3Entity);
+ PutObjectResponse objectResponse = s3.putObject(
+ PutObjectRequest.builder()
+ .bucket(s3Entity.getBucket().getName())
+ .key(key)
+ .build(),
+ RequestBody.fromBytes(inputStream.readAllBytes()));
+ if (objectResponse.sdkHttpResponse().isSuccessful()) {
+ System.out.println("File " + key + " created" );
+ }
+ }
+ }
+
+ private String getKey(Part part, S3EventNotification.S3Entity s3Entity) throws MessagingException {
+ return extractKeyPath(s3Entity.getObject().getKey()) +
+ "attachment/" +
+ getEmailId(s3Entity.getObject().getKey()) +
+ extractFileExtension(part.getFileName());
+ }
+
+ private String extractFileExtension(String fileName) {
+ return "." + fileName.substring(fileName.lastIndexOf(".") + 1);
+ }
+
+ private String extractKeyPath(String path) {
+ int lastIndex = path.lastIndexOf("/");
+ lastIndex = path.substring(0, lastIndex).lastIndexOf("/");
+ return path.substring(0, lastIndex) + "/";
+ }
+
+ private String getEmailId(String path) {
+ String[] parts = path.split("/");
+ return parts[parts.length - 1];
+ }
+
+}
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/util/EmailUtils.java b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/util/EmailUtils.java
new file mode 100644
index 0000000..d16d296
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/main/java/com/levio/awsdemo/attachmentsaver/util/EmailUtils.java
@@ -0,0 +1,28 @@
+package com.levio.awsdemo.attachmentsaver.util;
+
+import jakarta.mail.MessagingException;
+import jakarta.mail.Multipart;
+import jakarta.mail.Part;
+import jakarta.mail.internet.MimeMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmailUtils {
+ public static List extractAttachments(MimeMessage message) throws MessagingException, IOException {
+ Object content = message.getContent();
+
+ List attachments = new ArrayList<>();
+
+ if (content instanceof Multipart multipart) {
+ for (int i = 0; i < multipart.getCount(); i++) {
+ Part part = multipart.getBodyPart(i);
+ if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
+ attachments.add(part);
+ }
+ }
+ }
+ return attachments;
+ }
+}
diff --git a/lambdas/AttachmentSaver/AttachmentSaverFunction/src/test/java/com/levio/awsdemo/attachmentsaver/AppTest.java b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/test/java/com/levio/awsdemo/attachmentsaver/AppTest.java
new file mode 100644
index 0000000..cbee63c
--- /dev/null
+++ b/lambdas/AttachmentSaver/AttachmentSaverFunction/src/test/java/com/levio/awsdemo/attachmentsaver/AppTest.java
@@ -0,0 +1,22 @@
+//package com.levio.awsdemo.attachmentsaver;
+//
+//import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+//import static org.junit.Assert.assertEquals;
+//import static org.junit.Assert.assertNotNull;
+//import static org.junit.Assert.assertTrue;
+//import org.junit.Test;
+//
+//public class AppTest {
+// @Test
+// public void successfulResponse() {
+// App app = new App();
+// APIGatewayProxyResponseEvent result = app.handleRequest(null, null);
+// assertEquals(200, result.getStatusCode().intValue());
+// assertEquals("application/json", result.getHeaders().get("Content-Type"));
+// String content = result.getBody();
+// assertNotNull(content);
+// assertTrue(content.contains("\"message\""));
+// assertTrue(content.contains("\"hello world\""));
+// assertTrue(content.contains("\"location\""));
+// }
+//}
diff --git a/lambdas/ResumeProcessor/ResumeFunction/Dockerfile b/lambdas/ResumeProcessor/ResumeFunction/Dockerfile
new file mode 100644
index 0000000..81eb8ba
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/Dockerfile
@@ -0,0 +1,15 @@
+FROM public.ecr.aws/sam/build-java17:latest as build-image
+
+WORKDIR "/task"
+COPY src/ src/
+COPY pom.xml ./
+
+RUN mvn -q clean install
+RUN mvn dependency:copy-dependencies -DincludeScope=compile
+
+FROM public.ecr.aws/lambda/java:17
+
+COPY --from=build-image /task/target/classes /var/task/
+COPY --from=build-image /task/target/dependency /var/task/lib
+# Command can be overwritten by providing a different command in the template directly.
+CMD ["com.levio.awsdemo.resume.App::handleRequest"]
\ No newline at end of file
diff --git a/lambdas/ResumeProcessor/ResumeFunction/dependency-reduced-pom.xml b/lambdas/ResumeProcessor/ResumeFunction/dependency-reduced-pom.xml
new file mode 100644
index 0000000..f6e2612
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/dependency-reduced-pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+ com.levio.aws-demo
+ resume
+ Resume
+ 1.0
+
+
+
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+
+ 17
+ 17
+
+
diff --git a/lambdas/ResumeProcessor/ResumeFunction/events/event.json b/lambdas/ResumeProcessor/ResumeFunction/events/event.json
new file mode 100644
index 0000000..6f59884
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/events/event.json
@@ -0,0 +1,4 @@
+{
+ "prompt": "Refais ce texte sous forme de dialogues entre un intervenant et ses clients",
+ "text": "value2"
+}
\ No newline at end of file
diff --git a/lambdas/ResumeProcessor/ResumeFunction/iac/lambda.tf b/lambdas/ResumeProcessor/ResumeFunction/iac/lambda.tf
new file mode 100644
index 0000000..2bf193a
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/iac/lambda.tf
@@ -0,0 +1,56 @@
+data "aws_ecr_image" "lambda_image" {
+ repository_name = var.lambda_repository_name
+ most_recent = true
+}
+
+data "aws_caller_identity" "current" {}
+
+module "lambda_function_container_image" {
+ timeout = 60
+ source = "terraform-aws-modules/lambda/aws"
+ function_name = var.lambda_function_name
+ create_package = false
+ image_uri = data.aws_ecr_image.lambda_image.image_uri
+ package_type = "Image"
+ memory_size = 1024
+ role_name = "${var.lambda_function_name}-role"
+ attach_policy_statements = true
+
+ environment_variables = {
+ PROMPT = var.prompt_default
+ }
+
+ policy_statements = {
+ log_group = {
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogGroup"
+ ]
+ resources = [
+ "arn:aws:logs:*:*:*"
+ ]
+ }
+
+ log_write = {
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ]
+ resources = [
+ "arn:aws:logs:*:*:log-group:/aws/${var.lambda_function_name}/*:*"
+ ]
+ }
+
+ bedrock = {
+ effect = "Allow"
+ actions = [
+ "bedrock:InvokeModel"
+ ]
+ resources = [
+ "arn:aws:bedrock:*:*:*"
+ ]
+ }
+
+ }
+}
diff --git a/lambdas/ResumeProcessor/ResumeFunction/iac/output.tf b/lambdas/ResumeProcessor/ResumeFunction/iac/output.tf
new file mode 100644
index 0000000..9e15297
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/iac/output.tf
@@ -0,0 +1,3 @@
+output "lambda_function_arn" {
+ value = module.lambda_function_container_image.lambda_function_arn
+}
\ No newline at end of file
diff --git a/lambdas/ResumeProcessor/ResumeFunction/iac/variables.tf b/lambdas/ResumeProcessor/ResumeFunction/iac/variables.tf
new file mode 100644
index 0000000..9f4dfdf
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/iac/variables.tf
@@ -0,0 +1,19 @@
+variable "lambda_function_name" {
+ type = string
+ nullable = false
+}
+
+variable "lambda_repository_name" {
+ type = string
+ nullable = false
+}
+
+variable "prompt_default" {
+ type = string
+ nullable = false
+}
+
+variable "aws_region" {
+ type = string
+ default = "us-east-1"
+}
\ No newline at end of file
diff --git a/lambdas/ResumeProcessor/ResumeFunction/pom.xml b/lambdas/ResumeProcessor/ResumeFunction/pom.xml
new file mode 100644
index 0000000..7a32e3b
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/pom.xml
@@ -0,0 +1,67 @@
+
+ 4.0.0
+ com.levio.aws-demo
+ resume
+ 1.0
+ jar
+ Resume
+
+ 17
+ 17
+
+
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.2.1
+
+
+ com.amazonaws
+ aws-lambda-java-events
+ 3.11.0
+
+
+ software.amazon.awssdk
+ bedrockruntime
+ 2.21.17
+
+
+ software.amazon.awssdk
+ apache-client
+ 2.20.52
+
+
+ org.json
+ json
+ 20231013
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
diff --git a/lambdas/ResumeProcessor/ResumeFunction/src/main/java/com/levio/awsdemo/resume/App.java b/lambdas/ResumeProcessor/ResumeFunction/src/main/java/com/levio/awsdemo/resume/App.java
new file mode 100644
index 0000000..0e7a8d7
--- /dev/null
+++ b/lambdas/ResumeProcessor/ResumeFunction/src/main/java/com/levio/awsdemo/resume/App.java
@@ -0,0 +1,28 @@
+package com.levio.awsdemo.resume;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.levio.awsdemo.resume.service.BedrockService;
+
+import java.util.Map;
+
+public class App implements RequestHandler