diff --git a/src/main/java/com/safeqr/app/constants/APIConstants.java b/src/main/java/com/safeqr/app/constants/APIConstants.java index 8c0e85a..36cf11e 100644 --- a/src/main/java/com/safeqr/app/constants/APIConstants.java +++ b/src/main/java/com/safeqr/app/constants/APIConstants.java @@ -23,5 +23,9 @@ public class APIConstants { public static final String API_URL_USER_SET_BOOKMARK = "/user/setBookmark"; public static final String API_URL_USER_DELETE_BOOKMARK = "/user/deleteBookmark"; public static final String API_URL_USER_DELETE_ALL_BOOKMARK = "/user/deleteAllBookmark"; + public static final String API_URL_GMAIL_GET_EMAILS = "/gmail/getEmails"; + public static final String API_URL_GMAIL_GET_SCANNED_EMAILS = "/gmail/getScannedEmails"; + + } diff --git a/src/main/java/com/safeqr/app/gmail/controller/GmailController.java b/src/main/java/com/safeqr/app/gmail/controller/GmailController.java index de72cd3..126c907 100644 --- a/src/main/java/com/safeqr/app/gmail/controller/GmailController.java +++ b/src/main/java/com/safeqr/app/gmail/controller/GmailController.java @@ -27,8 +27,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.view.RedirectView; import static com.safeqr.app.constants.APIConstants.*; import java.io.IOException; +import java.lang.Thread; import java.util.*; - +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; @RestController @@ -100,14 +102,32 @@ public class GmailController { return new ResponseEntity<>(json.toString(), HttpStatus.OK); } - @GetMapping(value = "/gmail/getEmails", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getUserEmails(@RequestHeader(name = "accessToken") String accessToken) throws IOException, InterruptedException { + @GetMapping(value = API_URL_GMAIL_GET_SCANNED_EMAILS, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getUserScannedEmails(@RequestHeader(name = "X-USER-ID") String userId) { + logger.info("Invoking GET User scanned Emails endpoint"); + return ResponseEntity.ok(gmailService.fetchScannedGmail(userId)); + } + @GetMapping(value = API_URL_GMAIL_GET_EMAILS, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getUserEmails(@RequestHeader(name = "accessToken") String accessToken, + @RequestHeader(name = "refreshToken") String refreshToken, + @RequestHeader(name = "X-USER-ID") String userId + ) { logger.info("Invoking GET Scan User Emails endpoints"); if (accessToken == null || accessToken.isEmpty()) { return new ResponseEntity<>("Access token is missing", HttpStatus.BAD_REQUEST); } + logger.info("accessToken -> {}", accessToken); + logger.info("refreshToken -> {}", refreshToken); + logger.info("userId -> {}", userId); - return new ResponseEntity<>(gmailService.getEmail(accessToken), HttpStatus.OK); + CompletableFuture.runAsync(() -> { + gmailService.getEmailAsync(userId, accessToken, refreshToken); + }).exceptionally(throwable -> { + logger.error("Unexpected error occurred while processing emails", throwable); + return null; + }); + + return new ResponseEntity<>("Scan Gmail Request is being processed", HttpStatus.ACCEPTED); } } diff --git a/src/main/java/com/safeqr/app/gmail/entity/GmailCidEntity.java b/src/main/java/com/safeqr/app/gmail/entity/GmailCidEntity.java new file mode 100644 index 0000000..13478d8 --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/entity/GmailCidEntity.java @@ -0,0 +1,40 @@ +package com.safeqr.app.gmail.entity; + +import com.safeqr.app.qrcode.entity.QRCodeEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UuidGenerator; + +import java.util.UUID; + +@Entity +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "gmail_cid", schema = "safeqr") +public class GmailCidEntity { + @Id + @GeneratedValue(generator = "UUID") + @UuidGenerator + @Column(updatable = false, nullable = false) + private UUID id; + + @Column(name = "gmail_id") + private UUID gmailId; + + @Column(name = "cid") + private String cid; + + @Column(name = "attachment_id") + private String attachmentId; + + @Column(name = "decoded_content") + private String decodedContent; + + @Column(name = "qr_code_id") + private UUID qrCodeId; +} diff --git a/src/main/java/com/safeqr/app/gmail/entity/GmailEmailEntity.java b/src/main/java/com/safeqr/app/gmail/entity/GmailEmailEntity.java new file mode 100644 index 0000000..9ec084a --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/entity/GmailEmailEntity.java @@ -0,0 +1,52 @@ +package com.safeqr.app.gmail.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UuidGenerator; + +import java.time.OffsetDateTime; +import java.util.UUID; + +@Entity +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "gmail_emails", schema = "safeqr") +public class GmailEmailEntity { + @Id + @GeneratedValue(generator = "UUID") + @UuidGenerator + @Column(updatable = false, nullable = false) + private UUID id; + + @Column(name = "user_id") + private String userId; + + @Column(name = "message_id") + private String messageId; + + @Column(name = "thread_id") + private String threadId; + + @Column(name = "history_id") + private Long historyId; + + @Column(name= "subject") + private String subject; + + @Column(name = "date_received") + private OffsetDateTime dateReceived; + + @Column(name = "date_created") + private OffsetDateTime dateCreated; + + @PrePersist + public void prePersist() { + dateCreated = OffsetDateTime.now(); + } + +} diff --git a/src/main/java/com/safeqr/app/gmail/entity/GmailUrlEntity.java b/src/main/java/com/safeqr/app/gmail/entity/GmailUrlEntity.java new file mode 100644 index 0000000..4223ace --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/entity/GmailUrlEntity.java @@ -0,0 +1,38 @@ +package com.safeqr.app.gmail.entity; + +import com.safeqr.app.qrcode.entity.QRCodeEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UuidGenerator; + +import java.util.UUID; + +@Entity +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "gmail_urls", schema = "safeqr") +public class GmailUrlEntity { + @Id + @GeneratedValue(generator = "UUID") + @UuidGenerator + @Column(updatable = false, nullable = false) + private UUID id; + + @Column(name = "gmail_id") + private UUID gmailId; + + @Column(name = "image_url") + private String imageUrl; + + @Column(name = "decoded_content") + private String decodedContent; + + @Column(name = "qr_code_id") + private UUID qrCodeId; + +} diff --git a/src/main/java/com/safeqr/app/gmail/model/EmailMessage.java b/src/main/java/com/safeqr/app/gmail/model/EmailMessage.java index 79a13eb..e63a714 100644 --- a/src/main/java/com/safeqr/app/gmail/model/EmailMessage.java +++ b/src/main/java/com/safeqr/app/gmail/model/EmailMessage.java @@ -1,6 +1,7 @@ package com.safeqr.app.gmail.model; import com.fasterxml.jackson.annotation.JsonInclude; +import com.safeqr.app.qrcode.model.QRCodeModel; import lombok.Data; import java.util.ArrayList; @@ -8,6 +9,7 @@ import java.util.List; @Data public class EmailMessage { private String messageId; + private String threadId; private String subject; private String historyId; private String date; @@ -18,13 +20,18 @@ public class EmailMessage { @JsonInclude(JsonInclude.Include.NON_EMPTY) List qrCodeByURL; - public EmailMessage(String messageId, String subject, String historyId, String date) { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + List> decodedContentsDetails; + + public EmailMessage(String messageId, String threadId ,String subject, String historyId, String date) { this.messageId = messageId; + this.threadId = threadId; this.subject = subject; this.historyId = historyId; this.date = date; this.qrCodeByContentId = new ArrayList<>(); this.qrCodeByURL = new ArrayList<>(); + this.decodedContentsDetails = new ArrayList<>(); } public void addQRCodeByContentId(QRCodeByContentId qrCode) { this.qrCodeByContentId.add(qrCode); @@ -34,6 +41,10 @@ public class EmailMessage { this.qrCodeByURL.add(qrCode); } + public void addQRCodeModel(QRCodeModel qrCode) { + this.decodedContentsDetails.add(qrCode); + } + public boolean hasQRCodes() { return !qrCodeByContentId.isEmpty() || !qrCodeByURL.isEmpty(); } diff --git a/src/main/java/com/safeqr/app/gmail/model/QRCodeByContentId.java b/src/main/java/com/safeqr/app/gmail/model/QRCodeByContentId.java index d26e8cd..c083f79 100644 --- a/src/main/java/com/safeqr/app/gmail/model/QRCodeByContentId.java +++ b/src/main/java/com/safeqr/app/gmail/model/QRCodeByContentId.java @@ -1,5 +1,6 @@ package com.safeqr.app.gmail.model; +import com.safeqr.app.qrcode.model.QRCodeModel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,4 +15,8 @@ public class QRCodeByContentId { private String attachmentId; private List decodedContent; private int totalQRCodeFound; + + public List getDecodedContent() { + return decodedContent; + } } diff --git a/src/main/java/com/safeqr/app/gmail/repository/GmailCidRespository.java b/src/main/java/com/safeqr/app/gmail/repository/GmailCidRespository.java new file mode 100644 index 0000000..57a227a --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/repository/GmailCidRespository.java @@ -0,0 +1,11 @@ +package com.safeqr.app.gmail.repository; + +import com.safeqr.app.gmail.entity.GmailCidEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface GmailCidRespository extends JpaRepository { + List findByGmailId(UUID gmailId); +} diff --git a/src/main/java/com/safeqr/app/gmail/repository/GmailEmailRespository.java b/src/main/java/com/safeqr/app/gmail/repository/GmailEmailRespository.java new file mode 100644 index 0000000..a8850f8 --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/repository/GmailEmailRespository.java @@ -0,0 +1,12 @@ +package com.safeqr.app.gmail.repository; + + +import com.safeqr.app.gmail.entity.GmailEmailEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface GmailEmailRespository extends JpaRepository { + List findByUserId(String userId); +} diff --git a/src/main/java/com/safeqr/app/gmail/repository/GmailUrlsRespository.java b/src/main/java/com/safeqr/app/gmail/repository/GmailUrlsRespository.java new file mode 100644 index 0000000..ba0b48d --- /dev/null +++ b/src/main/java/com/safeqr/app/gmail/repository/GmailUrlsRespository.java @@ -0,0 +1,11 @@ +package com.safeqr.app.gmail.repository; + +import com.safeqr.app.gmail.entity.GmailUrlEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface GmailUrlsRespository extends JpaRepository { + List findByGmailId(UUID gmailId); +} diff --git a/src/main/java/com/safeqr/app/gmail/service/GmailService.java b/src/main/java/com/safeqr/app/gmail/service/GmailService.java index 676535d..3653f74 100644 --- a/src/main/java/com/safeqr/app/gmail/service/GmailService.java +++ b/src/main/java/com/safeqr/app/gmail/service/GmailService.java @@ -1,32 +1,53 @@ package com.safeqr.app.gmail.service; import com.google.api.client.auth.oauth2.BearerToken; +import com.google.api.client.auth.oauth2.ClientParametersAuthentication; +import com.google.api.client.auth.oauth2.Credential; + +import com.google.api.client.auth.oauth2.TokenResponse; +import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.gmail.Gmail; import com.google.api.services.gmail.model.*; import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.multi.qrcode.QRCodeMultiReader; +import com.google.zxing.qrcode.encoder.QRCode; import com.safeqr.app.gmail.dto.ScannedGmailResponseDto; +import com.safeqr.app.gmail.entity.GmailCidEntity; +import com.safeqr.app.gmail.entity.GmailEmailEntity; +import com.safeqr.app.gmail.entity.GmailUrlEntity; import com.safeqr.app.gmail.model.EmailMessage; import com.safeqr.app.gmail.model.QRCodeByContentId; import com.safeqr.app.gmail.model.QRCodeByURL; +import com.safeqr.app.gmail.repository.GmailCidRespository; +import com.safeqr.app.gmail.repository.GmailEmailRespository; +import com.safeqr.app.gmail.repository.GmailUrlsRespository; +import com.safeqr.app.qrcode.model.QRCodeModel; +import com.safeqr.app.qrcode.service.QRCodeTypeService; +import com.safeqr.app.user.service.UserService; +import com.safeqr.app.utils.DateParsingUtils; import org.apache.commons.codec.binary.Base64; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.services.gmail.Gmail; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.lang.Thread; import java.net.ConnectException; import java.net.URI; @@ -35,50 +56,288 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpTimeoutException; +import java.time.OffsetDateTime; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants.TOKEN_SERVER_URL; import static com.safeqr.app.constants.APIConstants.APPLICATION_NAME; @Service public class GmailService { + @Value("${gmail.client.clientId}") + private String clientId; + + @Value("${gmail.client.clientSecret}") + private String clientSecret; + + private final GmailEmailRespository gmailEmailRespository; + private final GmailCidRespository gmailCidRespository; + private final GmailUrlsRespository gmailUrlsRespository; + private final QRCodeTypeService qrCodeTypeService; + private final UserService userService; private static final Logger logger = LoggerFactory.getLogger(GmailService.class); private static final HttpTransport httpTransport = new NetHttpTransport(); private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final long MAX_RESULTS = 100L; - private Gmail getGmailService(String accessToken) { - Credential userCredentials = new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken); + public GmailService(GmailEmailRespository gmailEmailRespository, + GmailCidRespository gmailCidRespository, + GmailUrlsRespository gmailUrlsRespository, + QRCodeTypeService qrCodeTypeService, + UserService userService) { + this.gmailEmailRespository = gmailEmailRespository; + this.gmailCidRespository = gmailCidRespository; + this.gmailUrlsRespository = gmailUrlsRespository; + this.qrCodeTypeService = qrCodeTypeService; + this.userService = userService; + } + + private Gmail getGmailService(String accessToken, String refreshToken) throws IOException { + Credential userCredentials = new Credential.Builder(BearerToken.authorizationHeaderAccessMethod()) + .setTransport(httpTransport) + .setJsonFactory(JSON_FACTORY) + .setTokenServerUrl(new GenericUrl(TOKEN_SERVER_URL)) + .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) + .build() + .setAccessToken(accessToken) + .setRefreshToken(refreshToken); + return new Gmail.Builder(httpTransport, JSON_FACTORY, userCredentials) .setApplicationName(APPLICATION_NAME) .build(); } + // Renew the access token if it has expired using the refresh token. + private String refreshAccessToken(String refreshToken) throws IOException { + TokenResponse response = new GoogleRefreshTokenRequest( + httpTransport, JSON_FACTORY, refreshToken, clientId, clientSecret) + .execute(); + return response.getAccessToken(); + } + private Gmail refreshAndGetGmailService(String accessToken, String refreshToken) throws IOException { + try { + return getGmailService(accessToken, refreshToken); + } catch (GoogleJsonResponseException e) { + if (e.getStatusCode() == 401) { + logger.info("Access token expired. Refreshing token..."); + String newAccessToken = refreshAccessToken(refreshToken); + return getGmailService(newAccessToken, refreshToken); + } + throw e; + } + } + + // Async method to scan all emails in the user's inbox to prevent timeout. + @Async + public void getEmailAsync(String userId, String accessToken, String refreshToken) { + try { + ScannedGmailResponseDto result = getEmail(userId, accessToken, refreshToken); + CompletableFuture.completedFuture(result); + } catch (IOException e) { + logger.error("Error processing Gmail", e); + } + } // Scan all emails in the user's inbox. - public ScannedGmailResponseDto getEmail(String accessToken) throws IOException, InterruptedException { - Gmail service = getGmailService(accessToken); + public ScannedGmailResponseDto getEmail(String userId, String accessToken, String refreshToken) throws IOException { + Gmail service = refreshAndGetGmailService(accessToken, refreshToken); logger.info("Gmail service initialized: {}", service); List emailMessagesList = new ArrayList<>(); - String userId = "me"; + String meUserId = "me"; String nextPageToken = null; // Fetching email messages with page token and setting max results, Default value is 100. do { - ListMessagesResponse listResponse = fetchMessages(service, userId, nextPageToken); + +// ListHistoryResponse historyResponse = service.users().history().list(meUserId) +// .setStartHistoryId(BigInteger.valueOf(689335)) +// .execute(); +// +// List historyList = historyResponse.getHistory(); +// +// for (History history : historyList) { +// logger.info("History Id: {}, Message Id: {}, Message Snippet: {}", history.getId(), history.getMessages().get(0).getId(), history.getMessages().get(0).getHistoryId()); +// } + ListMessagesResponse listResponse = fetchMessages(service, meUserId, nextPageToken); List messages = listResponse.getMessages(); nextPageToken = listResponse.getNextPageToken(); - // Iterate all the messages and add to emailMessagesList only if it has a valid QR code. + + // Iterate all the messages and save to gmail db only if it has a valid QR code. for (Message message : messages) { - EmailMessage emailMessage = processMessage(service, userId, message); + EmailMessage emailMessage = processMessage(service, meUserId, message); if (emailMessage != null) { emailMessagesList.add(emailMessage); + // Save email message to database. + saveEmailMessageAndScanQRCode(userId, emailMessage); } } } while (nextPageToken != null); + // Update user's history id. + // TODO: Update user's history id. + return new ScannedGmailResponseDto(emailMessagesList); } + // Save email message to database and scan QR code. + private void saveEmailMessageAndScanQRCode(String userId, EmailMessage emailMessage) { + GmailEmailEntity gmailEmailEntity = saveEmailMessage(userId, emailMessage); + + if (gmailEmailEntity != null) { + // Save QR codes by content ID + saveQRCodeByContentId(gmailEmailEntity, emailMessage.getQrCodeByContentId()); + + // Save QR codes by URL + saveQRCodeByURL(gmailEmailEntity, emailMessage.getQrCodeByURL()); + } else { + logger.warn("Skipping QR code processing due to failure in saving email message."); + } + } + // Save to gmail_email table + private GmailEmailEntity saveEmailMessage(String userId, EmailMessage emailMessage) { + logger.info("userId: {}", userId); + OffsetDateTime dateReceived = DateParsingUtils.parseDate(emailMessage.getDate()); + try { + GmailEmailEntity gmailEmailEntity = GmailEmailEntity.builder() + .userId(userId) + .messageId(emailMessage.getMessageId()) + .threadId(emailMessage.getThreadId()) + .historyId(Long.valueOf(emailMessage.getHistoryId())) + .subject(emailMessage.getSubject()) + .dateReceived(dateReceived) + .build(); + return gmailEmailRespository.save(gmailEmailEntity); + } catch (DataIntegrityViolationException e) { + if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) { + logger.warn("Duplicate entry for userId: {}, messageId: {}", userId, emailMessage.getMessageId()); + } else { + logger.error("Error saving to gmail_email table: {}", e.getMessage(), e); + } + } catch (Exception e) { + logger.error("Error saving gmail_email table: {}", e.getMessage(), e); + } + return null; + } + + // Iterate through decoded contents found in attachment as content id and save to gmail_cid table + private void saveQRCodeByContentId(GmailEmailEntity gmailEmailEntity, List qrCodeByContentIdList) { + qrCodeByContentIdList.forEach(cid -> { + cid.getDecodedContent().forEach(decodedContent -> { + try { + QRCodeModel qrCodeModel = qrCodeTypeService.scanGmailDecodedContents(gmailEmailEntity.getUserId(), decodedContent); + GmailCidEntity gmailCidEntity = GmailCidEntity.builder() + .gmailId(gmailEmailEntity.getId()) + .qrCodeId(qrCodeModel.getData().getId()) + .cid(cid.getCid()) + .attachmentId(cid.getAttachmentId()) + .decodedContent(decodedContent) + .build(); + gmailCidRespository.save(gmailCidEntity); + } catch (Exception e) { + logger.error("Error saving QR code by content ID to gmail_cid table: {}", e.getMessage(), e); + } + }); + }); + } + // Iterate through decoded content found in url and save to gmail_url table + private void saveQRCodeByURL(GmailEmailEntity gmailEmailEntity, List qrCodeByURLList) { + qrCodeByURLList.forEach(imageUrl -> { + imageUrl.getDecodedContent().forEach(decodedContent -> { + try { + QRCodeModel qrCodeModel = qrCodeTypeService.scanGmailDecodedContents(gmailEmailEntity.getUserId(), decodedContent); + GmailUrlEntity gmailUrlEntity = GmailUrlEntity.builder() + .gmailId(gmailEmailEntity.getId()) + .qrCodeId(qrCodeModel.getData().getId()) + .imageUrl(imageUrl.getUrl()) + .decodedContent(decodedContent) + .build(); + gmailUrlsRespository.save(gmailUrlEntity); + } catch (Exception e) { + logger.error("Error saving QR code by URL to gmail_urls table: {}", e.getMessage(), e); + } + }); + }); + } + + // Fetching Scanned Gmail from database + public ScannedGmailResponseDto fetchScannedGmail(String userId){ + // Fetching all emails from gmail_email table + List userEmailsList = gmailEmailRespository.findByUserId(userId); + List emailMessageList = new ArrayList<>(); + + if (userEmailsList != null && !userEmailsList.isEmpty()) { + userEmailsList.forEach(email -> { + EmailMessage emailMessage = new EmailMessage( + email.getMessageId(), + email.getThreadId(), + email.getSubject(), + email.getHistoryId().toString(), + email.getDateReceived().toString() + ); + // Fetching all CIDs from gmail_cid table + List cidList = gmailCidRespository.findByGmailId(email.getId()); + Map qrCodeByContentIdMap = new HashMap<>(); + + for (GmailCidEntity cid : cidList) { + String key = cid.getCid() + "-" + cid.getAttachmentId(); + QRCodeByContentId qrCodeByContentId = qrCodeByContentIdMap.get(key); + + if (qrCodeByContentId == null) { + qrCodeByContentId = QRCodeByContentId.builder() + .cid(cid.getCid()) + .attachmentId(cid.getAttachmentId()) + .decodedContent(new ArrayList<>()) + .totalQRCodeFound(0) + .build(); + qrCodeByContentIdMap.put(key, qrCodeByContentId); + } + + // Append decoded content to the existing list + qrCodeByContentId.getDecodedContent().add(cid.getDecodedContent()); + + // Fetch scanned QR code from database and add to message object + emailMessage.addQRCodeModel(qrCodeTypeService.getScannedQRCodeDetailsInModel(cid.getQrCodeId())); + + qrCodeByContentId.setTotalQRCodeFound(qrCodeByContentId.getTotalQRCodeFound() + 1); + } + + emailMessage.setQrCodeByContentId(new ArrayList<>(qrCodeByContentIdMap.values())); + + // Fetching all URLs from gmail_urls table + List urlList = gmailUrlsRespository.findByGmailId(email.getId()); + + Map qrCodeByURLMap = new HashMap<>(); + + for (GmailUrlEntity url : urlList) { + String key = url.getImageUrl(); + QRCodeByURL qrCodeByURL = qrCodeByURLMap.get(key); + + if (qrCodeByURL == null) { + qrCodeByURL = QRCodeByURL.builder() + .url(url.getImageUrl()) + .decodedContent(new ArrayList<>()) + .totalQRCodeFound(0) + .build(); + qrCodeByURLMap.put(key, qrCodeByURL); + } + + // Append decoded content to the existing list + qrCodeByURL.getDecodedContent().add(url.getDecodedContent()); + + // Fetch scanned QR code from database and add to message object + emailMessage.addQRCodeModel(qrCodeTypeService.getScannedQRCodeDetailsInModel(url.getQrCodeId())); + qrCodeByURL.setTotalQRCodeFound(qrCodeByURL.getTotalQRCodeFound() + 1); + } + + emailMessage.setQrCodeByURL(new ArrayList<>(qrCodeByURLMap.values())); + + emailMessageList.add(emailMessage); + + }); + } + return ScannedGmailResponseDto.builder().messages(emailMessageList).build(); + } // Fetching email messages with page token and setting max results private ListMessagesResponse fetchMessages(Gmail service, String userId, String pageToken) throws IOException { return service.users().messages().list(userId) @@ -104,7 +363,7 @@ public class GmailService { logger.info("Message ID: {}", message.getId()); logger.info("History ID: {}", message.getHistoryId()); - EmailMessage emailMessage = new EmailMessage(message.getId(), subject, String.valueOf(message.getHistoryId()), emailDate); + EmailMessage emailMessage = new EmailMessage(message.getId(), message.getThreadId(), subject, String.valueOf(message.getHistoryId()), emailDate); processAttachments(service, message.getId(), parts, attachmentIds, emailMessage); processImageUrls(imageUrls, emailMessage); @@ -290,5 +549,4 @@ public class GmailService { String lowerUrl = url.toLowerCase(); return lowerUrl.endsWith(".jpg") || lowerUrl.endsWith(".jpeg") || lowerUrl.endsWith(".png") || lowerUrl.endsWith(".gif") || lowerUrl.endsWith(".bmp"); } - } diff --git a/src/main/java/com/safeqr/app/qrcode/model/URLModel.java b/src/main/java/com/safeqr/app/qrcode/model/URLModel.java index a646dce..5c5531e 100644 --- a/src/main/java/com/safeqr/app/qrcode/model/URLModel.java +++ b/src/main/java/com/safeqr/app/qrcode/model/URLModel.java @@ -1,7 +1,6 @@ package com.safeqr.app.qrcode.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.safeqr.app.qrcode.entity.EmailEntity; import com.safeqr.app.qrcode.entity.QRCodeEntity; import com.safeqr.app.qrcode.entity.URLEntity; import com.safeqr.app.qrcode.service.URLVerificationService; @@ -12,8 +11,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; -import java.net.URISyntaxException; - @EqualsAndHashCode(callSuper = true) @Data public final class URLModel extends QRCodeModel { diff --git a/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java b/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java index a84ccbd..765f620 100644 --- a/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java +++ b/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java @@ -68,13 +68,18 @@ public class QRCodeTypeService { } // Get scanned qrcode details public BaseScanResponse getScannedQRCodeDetails(UUID qrCodeId){ + return BaseScanResponse.builder().qrcode(getScannedQRCodeDetailsInModel(qrCodeId)).build(); + } + + public QRCodeModel getScannedQRCodeDetailsInModel(UUID qrCodeId){ // Find scanned qr code in qr code table QRCodeEntity qrCodeEntity = qrCodeRepository.findById(qrCodeId) .orElseThrow(() -> new ResourceNotFoundExceptions("QR Code not found with id: " + qrCodeId)); logger.info("qrCodeEntity: {}", qrCodeEntity); QRCodeModel qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(qrCodeEntity); logger.info("Retrieved details: {}", qrCodeModel.getDetails()); - return BaseScanResponse.builder().qrcode(qrCodeModel).build(); + + return qrCodeModel; } // Process Scanned QR Code @@ -109,6 +114,28 @@ public class QRCodeTypeService { return BaseScanResponse.builder().qrcode(qrCodeModel).build(); } + + @Transactional + public QRCodeModel scanGmailDecodedContents(String userId, String data) { + logger.info("Scan Gmail content: userId={}, data={}", userId, data); + + // Get the QR Code Type + QRCodeTypeEntity qrType = getQRCodeType(data); + + // Insert the QR Code into main qrcode table + QRCodeEntity scannedQR = qrCodeRepository.save(QRCodeEntity.builder() + .userId(userId) + .contents(data) + .info(qrType) + .createdAt(LocalDateTime.now()) + .build()); + + // Create the QR Code Instance based on the QR Code Type & insert into the respective table + QRCodeModel qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(scannedQR); + qrCodeModel.setDetails(); + + return qrCodeModel; + } // Returns Default type as text if it does not fit into any of the category private QRCodeTypeEntity getQRCodeType(String data) { return configs.stream() diff --git a/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java b/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java index c77f0ce..5649f1a 100644 --- a/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java +++ b/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java @@ -30,8 +30,9 @@ public class URLVerificationService { public URLEntity getURLEntityByQRCodeId(UUID qrCodeId) { logger.info("qrCodeId retrieving: {}", qrCodeId); - return urlRepository.findByQrCodeId(qrCodeId) - .orElseThrow(() -> new ResourceNotFoundExceptions("URL not found for QR Code id: " + qrCodeId)); +// return urlRepository.findByQrCodeId(qrCodeId) +// .orElseThrow(() -> new ResourceNotFoundExceptions("URL not found for QR Code id: " + qrCodeId)); + return urlRepository.findByQrCodeId(qrCodeId).orElse(null); } public void insertDB(URLEntity urlEntity) { diff --git a/src/main/java/com/safeqr/app/user/entity/UserEntity.java b/src/main/java/com/safeqr/app/user/entity/UserEntity.java index 7d3b4d6..04b7a40 100644 --- a/src/main/java/com/safeqr/app/user/entity/UserEntity.java +++ b/src/main/java/com/safeqr/app/user/entity/UserEntity.java @@ -33,4 +33,7 @@ public class UserEntity { @Column(name = "roles", columnDefinition = "text[]") private List roles; private String status; + + @Column(name = "gmail_history_id") + private Long gmailHistoryId; } diff --git a/src/main/java/com/safeqr/app/utils/AsyncConfig.java b/src/main/java/com/safeqr/app/utils/AsyncConfig.java new file mode 100644 index 0000000..13b1e56 --- /dev/null +++ b/src/main/java/com/safeqr/app/utils/AsyncConfig.java @@ -0,0 +1,34 @@ +package com.safeqr.app.utils; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.context.annotation.Bean; +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + + // Sets the number of core threads. These threads are always kept alive. + executor.setCorePoolSize(2); + + // Sets the maximum number of threads that can be created by the pool. + executor.setMaxPoolSize(2); + + // Sets the size of the queue to hold tasks before they are executed. + executor.setQueueCapacity(500); + + // Sets the prefix for the names of the threads created by this pool. + executor.setThreadNamePrefix("GmailProcessing-"); + + // Initializes the executor to apply the configuration and make it ready to use. + executor.initialize(); + + return executor; + } +} diff --git a/src/main/java/com/safeqr/app/utils/DateParsingUtils.java b/src/main/java/com/safeqr/app/utils/DateParsingUtils.java new file mode 100644 index 0000000..0ccc253 --- /dev/null +++ b/src/main/java/com/safeqr/app/utils/DateParsingUtils.java @@ -0,0 +1,42 @@ +package com.safeqr.app.utils; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.Locale; + +public class DateParsingUtils { + + private DateParsingUtils() { + // private constructor to hide the implicit public one + } + + private static final DateTimeFormatter INPUT_FORMATTER = new DateTimeFormatterBuilder() + .appendPattern("EEE, ") + .appendPattern("[ ]") // This makes a single space optional + .appendPattern("[ ]") // This allows for a second optional space + .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, java.time.format.SignStyle.NOT_NEGATIVE) + .appendPattern(" MMM yyyy HH:mm:ss Z") + .toFormatter(Locale.ENGLISH); + + private static final DateTimeFormatter OUTPUT_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS Z"); + + public static String parseAndFormatDate(String inputDate) { + try { + OffsetDateTime dateTime = OffsetDateTime.parse(inputDate, INPUT_FORMATTER); + return dateTime.format(OUTPUT_FORMATTER); + } catch (Exception e) { + throw new RuntimeException("Error parsing date: " + inputDate, e); + } + } + + public static OffsetDateTime parseDate(String inputDate) { + try { + return OffsetDateTime.parse(inputDate, INPUT_FORMATTER); + } catch (Exception e) { + throw new RuntimeException("Error parsing date: " + inputDate, e); + } + } +} \ No newline at end of file