Compare commits
108 Commits
feature-is
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61e8282e00 | ||
|
|
22bf62473f | ||
|
|
04e1ca12c8 | ||
|
|
2876be0d52 | ||
|
|
deab9666da | ||
|
|
878bfce095 | ||
|
|
9f4771f16d | ||
|
|
b8ee29ce9a | ||
|
|
0716214a31 | ||
|
|
e8836f1b5e | ||
|
|
d24ece60fd | ||
|
|
0301b0e1fb | ||
|
|
180f53dcaf | ||
|
|
3e4f18849e | ||
|
|
69c8bbb450 | ||
|
|
213a6dfc70 | ||
|
|
474d785c5c | ||
|
|
624bfdc2f9 | ||
|
|
3dda5fa770 | ||
|
|
b176e5c54f | ||
|
|
9adb53e7ab | ||
|
|
b86e680673 | ||
|
|
334b3867ec | ||
|
|
e6899eafdb | ||
|
|
f5d6396b06 | ||
|
|
393737e0f7 | ||
|
|
25711506b4 | ||
|
|
0592f6bfd1 | ||
|
|
28a16ead4f | ||
|
|
bbeb85bb0c | ||
|
|
5f55b073f3 | ||
|
|
08a9b3b630 | ||
|
|
8665693642 | ||
|
|
63e2d299fd | ||
|
|
32b604b172 | ||
|
|
c8cfe610a6 | ||
|
|
53f9acd922 | ||
|
|
e2ca15f556 | ||
|
|
ef1fb9f6e0 | ||
|
|
e51f99bc92 | ||
|
|
f2e92bd1ca | ||
|
|
cd047b33af | ||
|
|
a71cbd3093 | ||
|
|
d2c2767578 | ||
|
|
3b2533bb62 | ||
|
|
0901e7f07f | ||
|
|
080e695e5d | ||
|
|
4856417cf0 | ||
|
|
3fb5ad039a | ||
|
|
b55c615ed5 | ||
|
|
31476e4d75 | ||
|
|
529d27d07c | ||
|
|
3eb53c6ccd | ||
|
|
02085b50b9 | ||
|
|
2746891645 | ||
|
|
1d1ffcf5dc | ||
|
|
9d5b39e89c | ||
|
|
76036a2d91 | ||
|
|
dcd5058f70 | ||
|
|
0fde70a4b6 | ||
|
|
3567457026 | ||
|
|
20c9473bd3 | ||
|
|
31dbb35d00 | ||
|
|
a95e5e8fcd | ||
|
|
8cb63972a7 | ||
|
|
3ee3462fb5 | ||
|
|
8ca90d1507 | ||
|
|
430ed300e8 | ||
|
|
da12ec70c6 | ||
|
|
d29dddd133 | ||
|
|
7cb68e2ec0 | ||
|
|
77541c2a9d | ||
|
|
ee3180dbea | ||
|
|
f7e592f8ec | ||
|
|
a236eb13f8 | ||
|
|
0af328977b | ||
|
|
2771ac4f73 | ||
|
|
b966597ca6 | ||
|
|
686c4f939e | ||
|
|
3e0ad7db2d | ||
|
|
f65cee37f9 | ||
|
|
a02eaab0b1 | ||
|
|
d1dd29fa1d | ||
|
|
a08285ff43 | ||
|
|
f293353d36 | ||
|
|
1163648655 | ||
|
|
d273a2a085 | ||
|
|
8dad16f45a | ||
|
|
8f570a8605 | ||
|
|
41b02824cd | ||
|
|
7627c676fc | ||
|
|
e301b76dbf | ||
|
|
88bbcefc60 | ||
|
|
98878713d2 | ||
|
|
a8aa39b92f | ||
|
|
937bbaf3a7 | ||
|
|
4a3f389669 | ||
|
|
00a9b807bd | ||
|
|
c79ced4259 | ||
|
|
68b6a08310 | ||
|
|
23b0cea98b | ||
|
|
958f039a83 | ||
|
|
2c889f86f0 | ||
|
|
74ffd6885c | ||
|
|
36657ce742 | ||
|
|
be27afc248 | ||
|
|
e8fc84ba1c | ||
|
|
59cf2cdc3a |
4
.github/workflows/workflow.yml
vendored
4
.github/workflows/workflow.yml
vendored
@@ -37,6 +37,10 @@ jobs:
|
|||||||
echo "SERVER_SSL_KEY_ALIAS=${{ secrets.SERVER_SSL_KEY_ALIAS }}" >> $GITHUB_ENV
|
echo "SERVER_SSL_KEY_ALIAS=${{ secrets.SERVER_SSL_KEY_ALIAS }}" >> $GITHUB_ENV
|
||||||
echo "SERVER_SSL_TRUST_STORE_LOCATION=${{ secrets.SERVER_SSL_TRUST_STORE_LOCATION }}" >> $GITHUB_ENV
|
echo "SERVER_SSL_TRUST_STORE_LOCATION=${{ secrets.SERVER_SSL_TRUST_STORE_LOCATION }}" >> $GITHUB_ENV
|
||||||
echo "SERVER_SSL_TRUST_STORE_PASSWORD=${{ secrets.SERVER_SSL_TRUST_STORE_PASSWORD }}" >> $GITHUB_ENV
|
echo "SERVER_SSL_TRUST_STORE_PASSWORD=${{ secrets.SERVER_SSL_TRUST_STORE_PASSWORD }}" >> $GITHUB_ENV
|
||||||
|
echo "GOOGLE_SAFE_BROWSING_API_KEY=${{ secrets.GOOGLE_SAFE_BROWSING_API_KEY }}" >> $GITHUB_ENV
|
||||||
|
echo "VIRUSTOTAL_API_KEY=${{ secrets.VIRUSTOTAL_API_KEY }}" >> $GITHUB_ENV
|
||||||
|
echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV
|
||||||
|
echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn -B package --file pom.xml
|
run: mvn -B package --file pom.xml
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,6 +4,9 @@ target/
|
|||||||
!**/src/main/**/target/
|
!**/src/main/**/target/
|
||||||
!**/src/test/**/target/
|
!**/src/test/**/target/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*/.DS_Store
|
||||||
|
**/.DS_Store
|
||||||
|
credentials.json
|
||||||
|
|
||||||
### STS ###
|
### STS ###
|
||||||
.apt_generated
|
.apt_generated
|
||||||
@@ -36,3 +39,5 @@ build/
|
|||||||
|
|
||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# SafeQR Spring Boot Project
|
||||||
|
|
||||||
|
This is a Spring Boot project built with Java 17. This guide will help you set up the project and install the necessary dependencies using Maven.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have the following software installed on your system:
|
||||||
|
|
||||||
|
- **Java 17**: This project requires Java 17. You can download it from the [official Oracle website](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or install it via a package manager (e.g., `sdkman` or `brew` on macOS).
|
||||||
|
- **Maven 3.6+**: Apache Maven is used to manage the project's dependencies. You can download Maven from the [official Apache website](https://maven.apache.org/download.cgi) or install it via a package manager.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Follow these steps to set up and run the project locally:
|
||||||
|
|
||||||
|
### 1. Clone the Repository
|
||||||
|
|
||||||
|
Clone the project repository to your local machine using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/safeqr/backend-springboot.git
|
||||||
|
cd backend-springboot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Java and Maven Installation
|
||||||
|
|
||||||
|
Ensure that Java 17 and Maven are installed and available on your system by running the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -version
|
||||||
|
mvn -version
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see output indicating that Java 17 and Maven 3.6+ are installed.
|
||||||
|
|
||||||
|
### 3. Install Project Dependencies
|
||||||
|
|
||||||
|
Navigate to the root directory of the project (if you haven't already) and run the following command to clean the project and install all necessary dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn clean install
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will:
|
||||||
|
|
||||||
|
- **Clean**: Remove any previously compiled files.
|
||||||
|
- **Install**: Download all required dependencies as defined in the `pom.xml` file and compile the project.
|
||||||
|
|
||||||
|
### 4. Run the Application
|
||||||
|
|
||||||
|
Once the dependencies are installed, you can run the application with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start the Spring Boot application, and you should see the application logs in the terminal. By default, the application will be available at `http://localhost:8080`.
|
||||||
|
|
||||||
|
## Additional Information
|
||||||
|
|
||||||
|
- **Configuration**: Any necessary configurations can be adjusted in the `application.properties` or `application.yml` files located in the `src/main/resources` directory.
|
||||||
|
- **Building the Project**: To build a standalone JAR file, you can use `mvn package`, which will generate a JAR file in the `target` directory.
|
||||||
65
pom.xml
65
pom.xml
@@ -57,18 +57,71 @@
|
|||||||
<artifactId>reactor-core</artifactId>
|
<artifactId>reactor-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Reactor Core -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor</groupId>
|
|
||||||
<artifactId>reactor-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Netty DNS resolver for MacOS -->
|
<!-- Netty DNS resolver for MacOS -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-resolver-dns-native-macos</artifactId>
|
<artifactId>netty-resolver-dns-native-macos</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.hypersistence</groupId>
|
||||||
|
<artifactId>hypersistence-utils-hibernate-63</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.api-client</groupId>
|
||||||
|
<artifactId>google-api-client</artifactId>
|
||||||
|
<version>2.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.google.oauth-client/google-oauth-client-jetty -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.oauth-client</groupId>
|
||||||
|
<artifactId>google-oauth-client-jetty</artifactId>
|
||||||
|
<version>1.36.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.apis</groupId>
|
||||||
|
<artifactId>google-api-services-gmail</artifactId>
|
||||||
|
<version>v1-rev20240520-2.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.json/json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20240303</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
<version>3.5.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>javase</artifactId>
|
||||||
|
<version>3.5.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.18.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.17.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>2.17.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
BIN
src/main/.DS_Store
vendored
BIN
src/main/.DS_Store
vendored
Binary file not shown.
BIN
src/main/java/.DS_Store
vendored
BIN
src/main/java/.DS_Store
vendored
Binary file not shown.
BIN
src/main/java/com/.DS_Store
vendored
BIN
src/main/java/com/.DS_Store
vendored
Binary file not shown.
BIN
src/main/java/com/safeqr/.DS_Store
vendored
BIN
src/main/java/com/safeqr/.DS_Store
vendored
Binary file not shown.
BIN
src/main/java/com/safeqr/app/.DS_Store
vendored
BIN
src/main/java/com/safeqr/app/.DS_Store
vendored
Binary file not shown.
35
src/main/java/com/safeqr/app/constants/APIConstants.java
Normal file
35
src/main/java/com/safeqr/app/constants/APIConstants.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package com.safeqr.app.constants;
|
||||||
|
|
||||||
|
public class APIConstants {
|
||||||
|
private APIConstants() {
|
||||||
|
//private constructor to prevent instantiation
|
||||||
|
}
|
||||||
|
public static final String APPLICATION_NAME = "SafeQR";
|
||||||
|
public static final String API_VERSION = "v1";
|
||||||
|
public static final String API_URL_QRCODE_GET_ALL = "/qrcodetypes";
|
||||||
|
public static final String API_URL_QRCODE_SCAN = "/qrcodetypes/scan";
|
||||||
|
public static final String API_URL_QRCODE_DETECT = "/qrcodetypes/detect";
|
||||||
|
public static final String API_URL_QRCODE_VERIFY_URL = "/qrcodetypes/verifyURL";
|
||||||
|
public static final String API_URL_QRCODE_VIRUS_TOTAL_CHECK = "/qrcodetypes/virusTotalCheck";
|
||||||
|
public static final String API_URL_QRCODE_REDIRECT_COUNT = "/qrcodetypes/checkRedirects";
|
||||||
|
public static final String API_URL_QRCODE_GET_QR_DETAILS = "/qrcodetypes/getQRDetails";
|
||||||
|
public static final String PREDICTION_API_URL = "http://localhost:8000/predict";
|
||||||
|
|
||||||
|
|
||||||
|
public static final String API_URL_USER_GET = "/user/getUser";
|
||||||
|
public static final String API_URL_USER_GET_SCANNED_HISTORIES = "/user/getScannedHistories";
|
||||||
|
public static final String API_URL_USER_DELETE_SCANNED_HISTORIES = "/user/deleteScannedHistories";
|
||||||
|
public static final String API_URL_USER_DELETE_ALL_SCANNED_HISTORIES = "/user/deleteAllScannedHistories";
|
||||||
|
public static final String API_URL_USER_GET_BOOKMARKS = "/user/getBookmarks";
|
||||||
|
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";
|
||||||
|
public static final String API_URL_GMAIL_DELETE_MESSAGE = "/gmail/deleteMessage";
|
||||||
|
public static final String API_URL_GMAIL_DELETE_ALL_MESSAGES = "/gmail/deleteAllMessages";
|
||||||
|
|
||||||
|
public static final String API_URL_TIPS_GET = "/tips/getTips";
|
||||||
|
}
|
||||||
32
src/main/java/com/safeqr/app/constants/CommonConstants.java
Normal file
32
src/main/java/com/safeqr/app/constants/CommonConstants.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package com.safeqr.app.constants;
|
||||||
|
|
||||||
|
public class CommonConstants {
|
||||||
|
private CommonConstants() {
|
||||||
|
//private constructor to prevent instantiation
|
||||||
|
}
|
||||||
|
public static final String HEADER_USER_ID = "X-USER-ID";
|
||||||
|
public static final String DEFAULT_QR_CODE_TYPE = "TEXT";
|
||||||
|
public static final int MAX_REDIRECT_COUNT = 20;
|
||||||
|
public static final String QR_CODE_TYPE_EMAIL = "EMAIL";
|
||||||
|
public static final String QR_CODE_TYPE_PHONE = "PHONE";
|
||||||
|
public static final String QR_CODE_TYPE_SMS = "SMS";
|
||||||
|
public static final String QR_CODE_TYPE_URL = "URL";
|
||||||
|
public static final String QR_CODE_TYPE_WIFI = "WIFI";
|
||||||
|
|
||||||
|
public static final String INFO_NON_SECURE_CONNECTION = "Not an HTTPS connection";
|
||||||
|
public static final String INFO_NO_HSTS_HEADER = "No HSTS Header detected";
|
||||||
|
public static final String INFO_HSTS_HEADER_PREFIX = "HSTS Header: ";
|
||||||
|
public static final String INFO_HSTS_NOT_APPLICABLE = "N/A";
|
||||||
|
|
||||||
|
public static final String CLASSIFY_SAFE = "SAFE";
|
||||||
|
public static final String CLASSIFY_WARNING = "WARNING";
|
||||||
|
public static final String CLASSIFY_UNSAFE = "UNSAFE";
|
||||||
|
public static final String CLASSIFY_UNKNOWN = "UNKNOWN";
|
||||||
|
|
||||||
|
public static final String CAT_BENIGN = "Benign";
|
||||||
|
public static final String CAT_DEFACEMENT = "Defacement";
|
||||||
|
public static final String CAT_MALWARE = "Malware";
|
||||||
|
public static final String CAT_PHISHING = "Phishing";
|
||||||
|
|
||||||
|
public static final Integer GMAIL_ACTIVE = 1;
|
||||||
|
}
|
||||||
16
src/main/java/com/safeqr/app/exceptions/ErrorResponse.java
Normal file
16
src/main/java/com/safeqr/app/exceptions/ErrorResponse.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.safeqr.app.exceptions;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class ErrorResponse {
|
||||||
|
private String error;
|
||||||
|
private int status;
|
||||||
|
|
||||||
|
public ErrorResponse(String message, int status){
|
||||||
|
this.error = message;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.exceptions;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(ResourceNotFoundExceptions.class)
|
||||||
|
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundExceptions e) {
|
||||||
|
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
@ExceptionHandler(ResourceAlreadyExists.class)
|
||||||
|
public ResponseEntity<ErrorResponse> handleResourceAlreadyExistsException(ResourceAlreadyExists e) {
|
||||||
|
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
@ExceptionHandler(InvalidFormatExceptions.class)
|
||||||
|
public ResponseEntity<ErrorResponse> handleInvalidFormatException(InvalidFormatExceptions e) {
|
||||||
|
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.safeqr.app.exceptions;
|
||||||
|
|
||||||
|
public class InvalidFormatExceptions extends RuntimeException {
|
||||||
|
public InvalidFormatExceptions(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.safeqr.app.exceptions;
|
||||||
|
|
||||||
|
public class ResourceAlreadyExists extends RuntimeException {
|
||||||
|
public ResourceAlreadyExists(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.safeqr.app.exceptions;
|
||||||
|
|
||||||
|
public class ResourceNotFoundExceptions extends RuntimeException {
|
||||||
|
public ResourceNotFoundExceptions(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package com.safeqr.app.gmail.controller;
|
||||||
|
|
||||||
|
import com.safeqr.app.gmail.dto.MessageRequestDto;
|
||||||
|
import com.safeqr.app.gmail.dto.ScannedGmailResponseDto;
|
||||||
|
import com.safeqr.app.gmail.dto.BaseResponse;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
|
||||||
|
import com.google.api.client.auth.oauth2.Credential;
|
||||||
|
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||||
|
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
|
||||||
|
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
|
||||||
|
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
|
||||||
|
import com.google.api.client.http.HttpTransport;
|
||||||
|
import com.google.api.client.json.JsonFactory;
|
||||||
|
import com.google.api.client.json.gson.GsonFactory;
|
||||||
|
import com.google.api.services.gmail.GmailScopes;
|
||||||
|
import com.safeqr.app.gmail.service.GmailService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
import static com.safeqr.app.constants.APIConstants.*;
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.HEADER_USER_ID;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(API_VERSION)
|
||||||
|
public class GmailController {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GmailController.class);
|
||||||
|
GmailService gmailService;
|
||||||
|
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
|
||||||
|
private static com.google.api.services.gmail.Gmail client;
|
||||||
|
|
||||||
|
GoogleClientSecrets clientSecrets;
|
||||||
|
GoogleAuthorizationCodeFlow flow;
|
||||||
|
Credential credential;
|
||||||
|
|
||||||
|
@Value("${gmail.client.clientId}")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Value("${gmail.client.clientSecret}")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
@Value("${gmail.client.redirectUri}")
|
||||||
|
private String redirectUri;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public GmailController(GmailService gmailService) {
|
||||||
|
this.gmailService = gmailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/gmail/login")
|
||||||
|
public RedirectView googleConnectionStatus(HttpServletRequest request) throws Exception {
|
||||||
|
return new RedirectView(authorize());
|
||||||
|
}
|
||||||
|
private String authorize() throws Exception {
|
||||||
|
|
||||||
|
AuthorizationCodeRequestUrl authorizationUrl;
|
||||||
|
if (flow == null) {
|
||||||
|
GoogleClientSecrets.Details web = new GoogleClientSecrets.Details();
|
||||||
|
web.setClientId(clientId);
|
||||||
|
web.setClientSecret(clientSecret);
|
||||||
|
clientSecrets = new GoogleClientSecrets().setWeb(web);
|
||||||
|
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
|
||||||
|
flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, JSON_FACTORY, clientSecrets,
|
||||||
|
Collections.singleton(GmailScopes.GMAIL_READONLY))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
authorizationUrl = flow.newAuthorizationUrl()
|
||||||
|
.setRedirectUri(redirectUri)
|
||||||
|
.setAccessType("offline")
|
||||||
|
//.setApprovalPrompt("force") // force refresh token
|
||||||
|
;
|
||||||
|
|
||||||
|
logger.info("gmail authorizationUrl -> {}", authorizationUrl);
|
||||||
|
return authorizationUrl.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/gmail/callback", params = "code")
|
||||||
|
public ResponseEntity<String> oauth2Callback(@RequestParam(value = "code") String code) {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
try {
|
||||||
|
TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute();
|
||||||
|
credential = flow.createAndStoreCredential(response, "userID");
|
||||||
|
logger.info(credential.getAccessToken());
|
||||||
|
logger.info(credential.getRefreshToken());
|
||||||
|
logger.info(credential.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(json.toString(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = API_URL_GMAIL_GET_SCANNED_EMAILS, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<ScannedGmailResponseDto> getUserScannedEmails(@RequestHeader(name = "X-USER-ID") String userId) {
|
||||||
|
logger.info("User Id Invoking GET User scanned Emails endpoint: {}", userId);
|
||||||
|
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("User Id Invoking GET Scan User Emails endpoints: {}", userId);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_GMAIL_DELETE_MESSAGE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteMessage(@RequestHeader(name = HEADER_USER_ID) String userId, @RequestBody MessageRequestDto messageRequestDto) {
|
||||||
|
logger.info("User Id Invoking PUT Delete Single Email endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(gmailService.deleteMessage(userId, messageRequestDto.getMessageId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_GMAIL_DELETE_ALL_MESSAGES, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteAllMessages(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking PUT Delete All Emails endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(gmailService.deleteAllMessages(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
src/main/java/com/safeqr/app/gmail/dto/BaseResponse.java
Normal file
12
src/main/java/com/safeqr/app/gmail/dto/BaseResponse.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.safeqr.app.gmail.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BaseResponse {
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.gmail.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MessageRequestDto {
|
||||||
|
private String messageId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.safeqr.app.gmail.dto;
|
||||||
|
|
||||||
|
import com.safeqr.app.gmail.model.EmailMessage;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ScannedGmailResponseDto {
|
||||||
|
List<EmailMessage> messages;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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.math.BigInteger;
|
||||||
|
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 BigInteger historyId;
|
||||||
|
|
||||||
|
@Column(name= "subject")
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
@Column(name = "date_received")
|
||||||
|
private OffsetDateTime dateReceived;
|
||||||
|
|
||||||
|
@Column(name = "date_created")
|
||||||
|
private OffsetDateTime dateCreated;
|
||||||
|
|
||||||
|
@Column(name = "active")
|
||||||
|
private int active = 1;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
public void prePersist() {
|
||||||
|
dateCreated = OffsetDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
53
src/main/java/com/safeqr/app/gmail/model/EmailMessage.java
Normal file
53
src/main/java/com/safeqr/app/gmail/model/EmailMessage.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
|
import java.util.List;
|
||||||
|
@Data
|
||||||
|
public class EmailMessage {
|
||||||
|
private String messageId;
|
||||||
|
private String threadId;
|
||||||
|
private String subject;
|
||||||
|
private String historyId;
|
||||||
|
private String date;
|
||||||
|
private int active;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
List<QRCodeByContentId> qrCodeByContentId;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
List<QRCodeByURL> qrCodeByURL;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
List<QRCodeModel<?>> 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.active = 1;
|
||||||
|
this.qrCodeByContentId = new ArrayList<>();
|
||||||
|
this.qrCodeByURL = new ArrayList<>();
|
||||||
|
this.decodedContentsDetails = new ArrayList<>();
|
||||||
|
}
|
||||||
|
public void addQRCodeByContentId(QRCodeByContentId qrCode) {
|
||||||
|
this.qrCodeByContentId.add(qrCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addQRCodeByURL(QRCodeByURL qrCode) {
|
||||||
|
this.qrCodeByURL.add(qrCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addQRCodeModel(QRCodeModel<?> qrCode) {
|
||||||
|
this.decodedContentsDetails.add(qrCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasQRCodes() {
|
||||||
|
return !qrCodeByContentId.isEmpty() || !qrCodeByURL.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.safeqr.app.gmail.model;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QRCodeByContentId {
|
||||||
|
private String cid;
|
||||||
|
private String attachmentId;
|
||||||
|
private List<String> decodedContent;
|
||||||
|
private int totalQRCodeFound;
|
||||||
|
|
||||||
|
public List<String> getDecodedContent() {
|
||||||
|
return decodedContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/com/safeqr/app/gmail/model/QRCodeByURL.java
Normal file
17
src/main/java/com/safeqr/app/gmail/model/QRCodeByURL.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.safeqr.app.gmail.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QRCodeByURL {
|
||||||
|
private String url;
|
||||||
|
private List<String> decodedContent;
|
||||||
|
private int totalQRCodeFound;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<GmailCidEntity, UUID> {
|
||||||
|
List<GmailCidEntity> findByGmailId(UUID gmailId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.safeqr.app.gmail.repository;
|
||||||
|
|
||||||
|
|
||||||
|
import com.safeqr.app.gmail.entity.GmailEmailEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface GmailEmailRespository extends JpaRepository<GmailEmailEntity, UUID> {
|
||||||
|
// Method to find by userId and active status
|
||||||
|
List<GmailEmailEntity> findByUserIdAndActive(String userId, Integer active);
|
||||||
|
|
||||||
|
// Method to update active status to 0 for a specific userId
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE GmailEmailEntity e SET e.active = (SELECT MIN(e2.active) FROM GmailEmailEntity e2 WHERE e2.userId = :userId) - 1 " +
|
||||||
|
"WHERE e.userId = :userId " +
|
||||||
|
"AND e.active = 1 ")
|
||||||
|
int deactivateEmailsByUserId(String userId);
|
||||||
|
|
||||||
|
// Method to update active status to 0 for a specific userId and messageId
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE GmailEmailEntity e SET e.active = (SELECT MIN(e2.active) FROM GmailEmailEntity e2 WHERE e2.userId = :userId AND e2.messageId = :messageId) - 1 " +
|
||||||
|
"WHERE e.userId = :userId AND e.messageId = :messageId " +
|
||||||
|
"AND e.active = 1")
|
||||||
|
|
||||||
|
int deactivateEmailByUserIdAndMessageId(String userId, String messageId);
|
||||||
|
}
|
||||||
@@ -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<GmailUrlEntity, UUID> {
|
||||||
|
List<GmailUrlEntity> findByGmailId(UUID gmailId);
|
||||||
|
}
|
||||||
659
src/main/java/com/safeqr/app/gmail/service/GmailService.java
Normal file
659
src/main/java/com/safeqr/app/gmail/service/GmailService.java
Normal file
@@ -0,0 +1,659 @@
|
|||||||
|
package com.safeqr.app.gmail.service;
|
||||||
|
|
||||||
|
import com.google.api.client.auth.oauth2.*;
|
||||||
|
|
||||||
|
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.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.gmail.dto.BaseResponse;
|
||||||
|
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.entity.UserEntity;
|
||||||
|
import com.safeqr.app.user.service.UserService;
|
||||||
|
import com.safeqr.app.utils.DateParsingUtils;
|
||||||
|
import io.hypersistence.utils.common.StringUtils;
|
||||||
|
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 org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Thread;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
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 org.apache.http.auth.AuthenticationException;
|
||||||
|
|
||||||
|
import static com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants.TOKEN_SERVER_URL;
|
||||||
|
import static com.safeqr.app.constants.APIConstants.APPLICATION_NAME;
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.GMAIL_ACTIVE;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
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, AuthenticationException {
|
||||||
|
logger.info("Refresh token in refreshAccessToken: {}", refreshToken);
|
||||||
|
if (StringUtils.isBlank(refreshToken)) {
|
||||||
|
logger.error("Refresh token is null or empty");
|
||||||
|
throw new AuthenticationException("Invalid refresh token");
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxRetries = 3;
|
||||||
|
int retryCount = 0;
|
||||||
|
while (retryCount < maxRetries) {
|
||||||
|
try {
|
||||||
|
logger.info("Attempting to refresh access token (attempt {})", retryCount + 1);
|
||||||
|
TokenResponse response = new GoogleRefreshTokenRequest(
|
||||||
|
httpTransport, JSON_FACTORY, refreshToken, clientId, clientSecret)
|
||||||
|
.execute();
|
||||||
|
String newAccessToken = response.getAccessToken();
|
||||||
|
logger.info("Access token refreshed successfully");
|
||||||
|
return newAccessToken;
|
||||||
|
} catch (TokenResponseException e) {
|
||||||
|
logger.error("Failed to refresh access token. Status code: {}", e.getStatusCode());
|
||||||
|
logger.error("Error message: {}", e.getDetails().getError());
|
||||||
|
logger.error("Error description: {}", e.getDetails().getErrorDescription());
|
||||||
|
|
||||||
|
if (e.getStatusCode() == 401) {
|
||||||
|
logger.warn("Unauthorized error. The refresh token may be invalid or revoked.");
|
||||||
|
throw new AuthenticationException("Refresh token is invalid. User needs to re-authenticate.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++retryCount >= maxRetries) {
|
||||||
|
logger.error("Max retries reached. Unable to refresh access token.");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement exponential backoff
|
||||||
|
try {
|
||||||
|
Thread.sleep((long) (Math.pow(2, retryCount) * 1000));
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IOException("Token refresh interrupted", ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to refresh access token after " + maxRetries + " attempts");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Gmail refreshAndGetGmailService(String accessToken, String refreshToken) throws IOException, AuthenticationException {
|
||||||
|
try {
|
||||||
|
Gmail service = getGmailService(accessToken, refreshToken);
|
||||||
|
service.users().getProfile("me").execute();
|
||||||
|
logger.info("Gmail service authenticated with provided access token.");
|
||||||
|
return service;
|
||||||
|
} catch (GoogleJsonResponseException e) {
|
||||||
|
if (e.getStatusCode() == 401) {
|
||||||
|
logger.info("Access token expired. Refreshing...");
|
||||||
|
String newAccessToken = refreshAccessToken(refreshToken);
|
||||||
|
Gmail service = getGmailService(newAccessToken, refreshToken);
|
||||||
|
service.users().getProfile("me").execute();
|
||||||
|
logger.info("Gmail service authenticated with refreshed token.");
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
logger.error("Failed to authenticate with Gmail API", e);
|
||||||
|
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);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
logger.error("Error Authenticating", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Scan all emails in the user's inbox.
|
||||||
|
public ScannedGmailResponseDto getEmail(String userId, String accessToken, String refreshToken) throws IOException, AuthenticationException {
|
||||||
|
Gmail service = refreshAndGetGmailService(accessToken, refreshToken);
|
||||||
|
logger.info("Gmail service initialized: {}", service);
|
||||||
|
List<EmailMessage> emailMessagesList = new ArrayList<>();
|
||||||
|
String meUserId = "me";
|
||||||
|
String nextPageToken = null;
|
||||||
|
UserEntity userEntity = userService.getUserByIdForGmail(userId);
|
||||||
|
BigInteger historyId = userEntity.getGmailHistoryId();
|
||||||
|
|
||||||
|
// Fetch history if historyId is not 0 (Default db value)
|
||||||
|
if (historyId.compareTo(BigInteger.ZERO) != 0) {
|
||||||
|
logger.info("HistoryId from db: {}", historyId);
|
||||||
|
|
||||||
|
ListHistoryResponse historyResponse = service.users().history().list(meUserId)
|
||||||
|
.setStartHistoryId(historyId)
|
||||||
|
.execute();
|
||||||
|
List<History> historyList = historyResponse.getHistory();
|
||||||
|
if (historyList != null) {
|
||||||
|
for (History history : historyList) {
|
||||||
|
logger.info("History Response - History Id: {}, Message Id: {}", history.getId(), history.getMessages().get(0).getId());
|
||||||
|
List<Message> messages = history.getMessages();
|
||||||
|
for (Message message : messages) {
|
||||||
|
EmailMessage emailMessage = processMessage(service, meUserId, message);
|
||||||
|
if (emailMessage != null) {
|
||||||
|
emailMessagesList.add(emailMessage);
|
||||||
|
saveEmailMessageAndScanQRCode(userId, emailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fetching email messages with page token and setting max results, Default value is 100.
|
||||||
|
do {
|
||||||
|
ListMessagesResponse listResponse = fetchMessages(service, meUserId, nextPageToken);
|
||||||
|
List<Message> messages = listResponse.getMessages();
|
||||||
|
nextPageToken = listResponse.getNextPageToken();
|
||||||
|
|
||||||
|
// 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, meUserId, message);
|
||||||
|
if (emailMessage != null) {
|
||||||
|
emailMessagesList.add(emailMessage);
|
||||||
|
// Save email message to database.
|
||||||
|
saveEmailMessageAndScanQRCode(userId, emailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (nextPageToken != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user's history id.
|
||||||
|
BigInteger latestHistoryId = getLatestHistoryId(service, meUserId);
|
||||||
|
if (latestHistoryId != null) {
|
||||||
|
userEntity.setGmailHistoryId(latestHistoryId);
|
||||||
|
userService.updateUserEntity(userEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScannedGmailResponseDto(emailMessagesList);
|
||||||
|
}
|
||||||
|
private BigInteger getLatestHistoryId(Gmail service, String userId) throws IOException {
|
||||||
|
Profile profile = service.users().getProfile(userId).execute();
|
||||||
|
return profile.getHistoryId();
|
||||||
|
}
|
||||||
|
// 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(new BigInteger(emailMessage.getHistoryId()))
|
||||||
|
.subject(emailMessage.getSubject())
|
||||||
|
.dateReceived(dateReceived)
|
||||||
|
.active(emailMessage.getActive())
|
||||||
|
.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<QRCodeByContentId> 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<QRCodeByURL> 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<GmailEmailEntity> userEmailsList = gmailEmailRespository.findByUserIdAndActive(userId, GMAIL_ACTIVE);
|
||||||
|
List<EmailMessage> 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<GmailCidEntity> cidList = gmailCidRespository.findByGmailId(email.getId());
|
||||||
|
Map<String, QRCodeByContentId> 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<GmailUrlEntity> urlList = gmailUrlsRespository.findByGmailId(email.getId());
|
||||||
|
|
||||||
|
Map<String, QRCodeByURL> 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);
|
||||||
|
|
||||||
|
});
|
||||||
|
emailMessageList.sort(Comparator.comparing(EmailMessage::getDate, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
.setPageToken(pageToken)
|
||||||
|
.setMaxResults(MAX_RESULTS)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
// Processing email message and returning EmailMessage object if it has a valid QR code.
|
||||||
|
private EmailMessage processMessage(Gmail service, String userId, Message message) {
|
||||||
|
try {
|
||||||
|
message = service.users().messages().get(userId, message.getId()).setFormat("full").execute();
|
||||||
|
List<MessagePart> parts = message.getPayload().getParts();
|
||||||
|
Set<String> attachmentIds = new HashSet<>();
|
||||||
|
Set<String> imageUrls = new HashSet<>();
|
||||||
|
processPartsRecursively(parts, attachmentIds, imageUrls);
|
||||||
|
|
||||||
|
if (attachmentIds.isEmpty() && imageUrls.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subject = getHeader(message, "Subject");
|
||||||
|
String emailDate = getHeader(message, "Date");
|
||||||
|
logger.info("Email Subject: {}", subject);
|
||||||
|
logger.info("Message ID: {}", message.getId());
|
||||||
|
logger.info("History ID: {}", message.getHistoryId());
|
||||||
|
|
||||||
|
EmailMessage emailMessage = new EmailMessage(message.getId(), message.getThreadId(), subject, String.valueOf(message.getHistoryId()), emailDate);
|
||||||
|
|
||||||
|
processAttachments(service, message.getId(), parts, attachmentIds, emailMessage);
|
||||||
|
processImageUrls(imageUrls, emailMessage);
|
||||||
|
|
||||||
|
return emailMessage.hasQRCodes() ? emailMessage : null;
|
||||||
|
} catch (GoogleJsonResponseException e) {
|
||||||
|
if (e.getStatusCode() == 404) {
|
||||||
|
logger.warn("Message with ID {} not found. It may have been deleted.", message.getId());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
logger.error("Error processing message with ID {}: {}", message.getId(), e.getMessage());
|
||||||
|
throw new RuntimeException("Error processing Gmail message", e);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("IO error processing message with ID {}: {}", message.getId(), e.getMessage());
|
||||||
|
throw new RuntimeException("IO error processing Gmail message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process all the attachments.
|
||||||
|
private void processAttachments(Gmail service, String messageId, List<MessagePart> parts, Set<String> attachmentIds, EmailMessage emailMessage) throws IOException {
|
||||||
|
for (String attachmentId : attachmentIds) {
|
||||||
|
Optional<String> attachment = findAttachmentIdByCid(parts, attachmentId);
|
||||||
|
if (attachment.isPresent()) {
|
||||||
|
List<String> qrCodeValue = processAttachment(service, messageId, attachment.get());
|
||||||
|
if (!qrCodeValue.isEmpty()) {
|
||||||
|
emailMessage.addQRCodeByContentId(new QRCodeByContentId(attachmentId, attachment.get(), qrCodeValue, qrCodeValue.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process all the image URLs.
|
||||||
|
private void processImageUrls(Set<String> imageUrls, EmailMessage emailMessage) {
|
||||||
|
for (String imageUrl : imageUrls) {
|
||||||
|
List<String> qrCodeValue = scanQRCodeFromUrl(imageUrl);
|
||||||
|
if (!qrCodeValue.isEmpty()) {
|
||||||
|
emailMessage.addQRCodeByURL(new QRCodeByURL(imageUrl, qrCodeValue, qrCodeValue.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find the header with the given name.
|
||||||
|
private String getHeader(Message message, String name) {
|
||||||
|
return message.getPayload().getHeaders().stream()
|
||||||
|
.filter(header -> name.equalsIgnoreCase(header.getName()))
|
||||||
|
.findFirst()
|
||||||
|
.map(MessagePartHeader::getValue)
|
||||||
|
.orElse("No " + name);
|
||||||
|
}
|
||||||
|
// Find the attachment ID in the given message part.
|
||||||
|
private Optional<String> findAttachmentIdByCid(List<MessagePart> parts, String cid) {
|
||||||
|
return parts.stream()
|
||||||
|
.flatMap(part -> Stream.concat(findAttachmentIdInCurrentPart(part, cid).stream(), Optional.ofNullable(part.getParts())
|
||||||
|
.flatMap(subParts -> findAttachmentIdByCid(subParts, cid)).stream()))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
// Find the attachment ID in the message subpart.
|
||||||
|
private Optional<String> findAttachmentIdInCurrentPart(MessagePart part, String cid) {
|
||||||
|
return Optional.ofNullable(part.getHeaders())
|
||||||
|
.flatMap(headers -> headers.stream()
|
||||||
|
.filter(header -> isContentIdHeader(header, cid))
|
||||||
|
.findFirst()
|
||||||
|
.map(header -> part.getBody().getAttachmentId()));
|
||||||
|
}
|
||||||
|
// Check if the header is a Content-ID header with the given CID.
|
||||||
|
private boolean isContentIdHeader(MessagePartHeader header, String cid) {
|
||||||
|
return "Content-ID".equalsIgnoreCase(header.getName()) && header.getValue().contains(cid);
|
||||||
|
}
|
||||||
|
// Recursive method to handle nested parts to search for CID URIs
|
||||||
|
private void processPartsRecursively(List<MessagePart> parts, Set<String> attachmentIds, Set<String> imageURLs) {
|
||||||
|
if (parts != null) {
|
||||||
|
for (MessagePart part : parts) {
|
||||||
|
if (part.getMimeType().equalsIgnoreCase("text/html")) {
|
||||||
|
String html = new String(Base64.decodeBase64(part.getBody().getData()));
|
||||||
|
attachmentIds.addAll(extractCIDsFromHtml(html));
|
||||||
|
imageURLs.addAll(extractImageUrlsFromHtml(html));
|
||||||
|
} else if (part.getParts() != null) {
|
||||||
|
// Recursive call to handle nested parts
|
||||||
|
processPartsRecursively(part.getParts(), attachmentIds, imageURLs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private List<String> scanQRCodeFromUrl(String imageUrl) {
|
||||||
|
try {
|
||||||
|
BufferedImage image = downloadImageFromUrl(imageUrl);
|
||||||
|
if (image != null) {
|
||||||
|
return decodeQRCodes(image);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("Invalid URI scheme for URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
} catch(URISyntaxException e) {
|
||||||
|
logger.error("Error while scanning QR code from URL", e);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
// Download the image from the given URL
|
||||||
|
private BufferedImage downloadImageFromUrl(String imageUrl) throws URISyntaxException {
|
||||||
|
try {
|
||||||
|
imageUrl = imageUrl.replace(" ", "%20");
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||||
|
.build();
|
||||||
|
logger.info("Downloading image from URL: {}", imageUrl);
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(new URI(imageUrl))
|
||||||
|
.header("User-Agent", "Mozilla/5.0")
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||||
|
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
byte[] imageBytes = response.body();
|
||||||
|
return ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||||
|
} else {
|
||||||
|
logger.warn("Failed to download image. HTTP response code: {}", response.statusCode());
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
logger.error("Invalid URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
} catch (HttpTimeoutException e) {
|
||||||
|
logger.error("Request timed out for URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
} catch (ConnectException e) {
|
||||||
|
logger.warn("Failed to connect to URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Error downloading image from URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
logger.warn("Thread was interrupted during IO operation for URL: {}", imageUrl);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("Thread was interrupted during HTTP request for URL: {} -> {}", imageUrl, e.getMessage());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private List<String> processAttachment(Gmail service, String messageId, String attachmentId) throws IOException {
|
||||||
|
MessagePartBody attachPart = service.users().messages().attachments().get("me", messageId, attachmentId).execute();
|
||||||
|
byte[] imageBytes = Base64.decodeBase64(attachPart.getData());
|
||||||
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||||
|
// ImageIO.write(image, "png", new File("debug_image.png"));
|
||||||
|
return decodeQRCodes(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> decodeQRCodes(BufferedImage image) {
|
||||||
|
List<String> qrCodeValues = new ArrayList<>();
|
||||||
|
LuminanceSource source = new BufferedImageLuminanceSource(image);
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
// Set up decoding hints
|
||||||
|
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||||
|
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
|
||||||
|
hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE));
|
||||||
|
|
||||||
|
try {
|
||||||
|
QRCodeMultiReader multiReader = new QRCodeMultiReader();
|
||||||
|
Result[] results = multiReader.decodeMultiple(bitmap, hints);
|
||||||
|
|
||||||
|
if (results != null) {
|
||||||
|
for (Result result : results) {
|
||||||
|
qrCodeValues.add(result.getText());
|
||||||
|
logger.info("Detected QR code: {}", result.getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
// No QR codes found
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error decoding QR codes", e);
|
||||||
|
}
|
||||||
|
if (!qrCodeValues.isEmpty())
|
||||||
|
logger.info("Total QR codes found: {}", qrCodeValues.size());
|
||||||
|
return qrCodeValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Extract CIDs from HTML
|
||||||
|
private Set<String> extractCIDsFromHtml(String html) {
|
||||||
|
Document doc = Jsoup.parse(html);
|
||||||
|
Elements imgs = doc.select("img[src^=cid:]");
|
||||||
|
|
||||||
|
return imgs.stream()
|
||||||
|
.map(img -> img.attr("src"))
|
||||||
|
.filter(src -> src.startsWith("cid:"))
|
||||||
|
.map(src -> src.substring(4)) // Remove "cid:" prefix
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
//Extract image URLs from HTML
|
||||||
|
private Set<String> extractImageUrlsFromHtml(String html) {
|
||||||
|
Document doc = Jsoup.parse(html);
|
||||||
|
Elements imgs = doc.select("img[src]");
|
||||||
|
|
||||||
|
return imgs.stream()
|
||||||
|
.map(img -> img.attr("src"))
|
||||||
|
.filter(this::isImageUrl)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
// Check if the URL is an image URL
|
||||||
|
private boolean isImageUrl(String url) {
|
||||||
|
String lowerUrl = url.toLowerCase();
|
||||||
|
return lowerUrl.endsWith(".jpg") || lowerUrl.endsWith(".jpeg") || lowerUrl.endsWith(".png") || lowerUrl.endsWith(".gif") || lowerUrl.endsWith(".bmp");
|
||||||
|
}
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteMessage(String userId, String messageId) {
|
||||||
|
int updatedCount = gmailEmailRespository.deactivateEmailByUserIdAndMessageId(userId, messageId);
|
||||||
|
// throw exception if email message not found
|
||||||
|
if (updatedCount < 1)
|
||||||
|
throw new ResourceNotFoundExceptions("Email message not found");
|
||||||
|
|
||||||
|
return BaseResponse.builder().message("Email deleted successfully").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteAllMessages(String userId) {
|
||||||
|
int updatedCount = gmailEmailRespository.deactivateEmailsByUserId(userId);
|
||||||
|
return (updatedCount < 1) ?
|
||||||
|
BaseResponse.builder().message("No Email found").build() :
|
||||||
|
BaseResponse.builder().message("All Emails deleted successfully").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package com.safeqr.app.prediction.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.URLModel;
|
||||||
|
import lombok.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class URLFeaturesMapper {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(URLFeaturesMapper.class);
|
||||||
|
|
||||||
|
@JsonProperty("domain")
|
||||||
|
private Integer domain;
|
||||||
|
|
||||||
|
@JsonProperty("subdomain")
|
||||||
|
private Integer subdomain;
|
||||||
|
|
||||||
|
@JsonProperty("top_level_domain")
|
||||||
|
private Integer topLevelDomain;
|
||||||
|
|
||||||
|
@JsonProperty("query")
|
||||||
|
private Integer query;
|
||||||
|
|
||||||
|
@JsonProperty("fragment")
|
||||||
|
private Integer fragment;
|
||||||
|
|
||||||
|
@JsonProperty("redirect")
|
||||||
|
private Integer redirect;
|
||||||
|
|
||||||
|
@JsonProperty("path")
|
||||||
|
private Integer path;
|
||||||
|
|
||||||
|
@JsonProperty("redirect_chain")
|
||||||
|
private Integer redirectChain;
|
||||||
|
|
||||||
|
@JsonProperty("hsts_header")
|
||||||
|
private Integer hstsHeader;
|
||||||
|
|
||||||
|
@JsonProperty("ssl_stripping")
|
||||||
|
private Integer sslStripping;
|
||||||
|
|
||||||
|
@JsonProperty("hostname_embedding")
|
||||||
|
private Integer hostnameEmbedding;
|
||||||
|
|
||||||
|
@JsonProperty("javascript_check")
|
||||||
|
private Integer javascriptCheck;
|
||||||
|
|
||||||
|
@JsonProperty("shortening_service")
|
||||||
|
private Integer shorteningService;
|
||||||
|
|
||||||
|
@JsonProperty("has_ip_address")
|
||||||
|
private Integer hasIpAddress;
|
||||||
|
|
||||||
|
@JsonProperty("tracking_descriptions")
|
||||||
|
private Integer trackingDescriptions;
|
||||||
|
|
||||||
|
@JsonProperty("url_encoding")
|
||||||
|
private Integer urlEncoding;
|
||||||
|
|
||||||
|
@JsonProperty("has_executable")
|
||||||
|
private Integer hasExecutable;
|
||||||
|
|
||||||
|
@JsonProperty("tls")
|
||||||
|
private Integer tls;
|
||||||
|
|
||||||
|
@JsonProperty("contents")
|
||||||
|
private Integer contents;
|
||||||
|
|
||||||
|
public static URLFeaturesMapper fromEntity(URLModel urlModel) {
|
||||||
|
URLEntity details = urlModel.getDetails();
|
||||||
|
QRCodeTypeEntity qrCodeTypeEntity = urlModel.getData().getInfo();
|
||||||
|
URLFeaturesMapper features = URLFeaturesMapper.builder()
|
||||||
|
.build();
|
||||||
|
features.setDomain(details.getDomain());
|
||||||
|
features.setSubdomain(details.getSubdomain());
|
||||||
|
features.setTopLevelDomain(details.getTopLevelDomain());
|
||||||
|
features.setQuery(details.getQuery());
|
||||||
|
features.setFragment(details.getFragment());
|
||||||
|
features.setPath(details.getPath());
|
||||||
|
features.setRedirect(details.getRedirect());
|
||||||
|
features.setRedirectChain(details.getRedirectChain());
|
||||||
|
features.setHstsHeader(details.getHstsHeader());
|
||||||
|
features.setSslStripping(details.getSslStripping());
|
||||||
|
features.setHostnameEmbedding(details.getHostnameEmbedding());
|
||||||
|
features.setJavascriptCheck(details.getJavascriptCheck());
|
||||||
|
features.setShorteningService(details.getShorteningService());
|
||||||
|
features.setHasIpAddress(details.getHasIpAddress());
|
||||||
|
features.setTrackingDescriptions(details.getTrackingDescriptions());
|
||||||
|
features.setUrlEncoding(details.getUrlEncoding());
|
||||||
|
features.setHasExecutable(details.getHasExecutable());
|
||||||
|
features.setTls(Math.toIntExact(qrCodeTypeEntity.getId()));
|
||||||
|
features.setContents(urlModel.getData().getContents());
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRedirect(int redirect) {
|
||||||
|
this.redirect = redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom setter for tls (qr_code_type_id)
|
||||||
|
public void setTls(Integer tls) {
|
||||||
|
if (tls != null) {
|
||||||
|
this.tls = tls == 1 ? 0 : tls == 9 ? 1 : tls.intValue();
|
||||||
|
} else {
|
||||||
|
this.tls = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom setter for hostnameEmbedding and other similar columns
|
||||||
|
public void setHostnameEmbedding(Integer hostnameEmbedding) {
|
||||||
|
this.hostnameEmbedding = (hostnameEmbedding != null && hostnameEmbedding != 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavascriptCheck(String javascriptCheck) {
|
||||||
|
this.javascriptCheck = (javascriptCheck != null && !javascriptCheck.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShorteningService(String shorteningService) {
|
||||||
|
this.shorteningService = (shorteningService != null && !shorteningService.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasIpAddress(String hasIpAddress) {
|
||||||
|
this.hasIpAddress = (hasIpAddress != null && !hasIpAddress.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlEncoding(String urlEncoding) {
|
||||||
|
this.urlEncoding = (urlEncoding != null && !urlEncoding.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasExecutable(String hasExecutable) {
|
||||||
|
this.hasExecutable = (hasExecutable != null && !hasExecutable.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrackingDescriptions(List<String> trackingDescriptions) {
|
||||||
|
this.trackingDescriptions = (trackingDescriptions != null && !trackingDescriptions.isEmpty()) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom setter for sslStripping
|
||||||
|
public void setSslStripping(List<Boolean> sslStripping) {
|
||||||
|
if (sslStripping != null && !sslStripping.isEmpty() && sslStripping.get(0) != null) {
|
||||||
|
this.sslStripping = sslStripping.get(0) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
this.sslStripping = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom setter for hstsHeader
|
||||||
|
public void setHstsHeader(List<String> hstsHeader) {
|
||||||
|
logger.info("HSTS header value: {}", hstsHeader);
|
||||||
|
if (hstsHeader == null || hstsHeader.isEmpty()) {
|
||||||
|
this.hstsHeader = 0;
|
||||||
|
} else {
|
||||||
|
logger.info("first hsts header value: {}", hstsHeader.get(0));
|
||||||
|
if (hstsHeader.get(0).toLowerCase().contains("no")) {
|
||||||
|
this.hstsHeader = 0;
|
||||||
|
} else {
|
||||||
|
this.hstsHeader = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom setters for calculating string lengths
|
||||||
|
public void setDomain(String domain) {
|
||||||
|
this.domain = (domain != null) ? domain.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubdomain(String subdomain) {
|
||||||
|
this.subdomain = (subdomain != null) ? subdomain.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopLevelDomain(String topLevelDomain) {
|
||||||
|
this.topLevelDomain = (topLevelDomain != null) ? topLevelDomain.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(String query) {
|
||||||
|
this.query = (query != null) ? query.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFragment(String fragment) {
|
||||||
|
this.fragment = (fragment != null) ? fragment.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPath(String path) {
|
||||||
|
this.path = (path != null) ? path.length() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirectChain(List<String> redirectChain) {
|
||||||
|
logger.info("Redirect chain: {}", redirectChain);
|
||||||
|
if (redirectChain != null) {
|
||||||
|
// Calculate the total number of characters in the list of strings
|
||||||
|
int totalChars;
|
||||||
|
totalChars = redirectChain.stream()
|
||||||
|
.mapToInt(String::length)
|
||||||
|
.sum();
|
||||||
|
this.redirectChain = totalChars;
|
||||||
|
} else {
|
||||||
|
this.redirectChain = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContents(String contents) {
|
||||||
|
this.contents = (contents != null) ? contents.length() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.safeqr.app.prediction.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.safeqr.app.prediction.model.URLFeaturesMapper;
|
||||||
|
import com.safeqr.app.qrcode.model.URLModel;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.APIConstants.PREDICTION_API_URL;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PredictionService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PredictionService.class);
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public PredictionService(RestTemplate restTemplate, ObjectMapper objectMapper) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String predict(URLModel urlModel) {
|
||||||
|
// Convert URLModel to URLFeatures
|
||||||
|
URLFeaturesMapper features = URLFeaturesMapper.fromEntity(urlModel);
|
||||||
|
logger.info("Prediction request: {}", features);
|
||||||
|
logger.info("feature contents : {}", features.getContents());
|
||||||
|
logger.info("feature domain : {}", features.getDomain());
|
||||||
|
logger.info("feature sub-domain : {}", features.getSubdomain());
|
||||||
|
logger.info("feature tld : {}", features.getTopLevelDomain());
|
||||||
|
logger.info("feature path : {}", features.getPath());
|
||||||
|
logger.info("feature query : {}", features.getQuery());
|
||||||
|
logger.info("feature fragment : {}", features.getFragment());
|
||||||
|
logger.info("feature redirect : {}", features.getRedirect());
|
||||||
|
logger.info("feature redirect chain: {}", features.getRedirectChain());
|
||||||
|
logger.info("feature shortening service: {}", features.getShorteningService());
|
||||||
|
logger.info("feature hasExecutable: {}", features.getHasExecutable());
|
||||||
|
logger.info("feature hasIP: {}", features.getHasIpAddress());
|
||||||
|
logger.info("feature hostname embedding: {}", features.getHostnameEmbedding());
|
||||||
|
logger.info("feature hsts header: {}", features.getHstsHeader());
|
||||||
|
logger.info("feature javascript check: {}", features.getJavascriptCheck());
|
||||||
|
logger.info("feature tracking: {}", features.getTrackingDescriptions());
|
||||||
|
logger.info("feature urlencoding: {}", features.getUrlEncoding());
|
||||||
|
|
||||||
|
// Prepare the HTTP headers
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
// Create the HTTP entity containing the features and headers
|
||||||
|
HttpEntity<URLFeaturesMapper> requestEntity = new HttpEntity<>(features, headers);
|
||||||
|
|
||||||
|
// Make the HTTP POST request to the FastAPI prediction endpoint
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
PREDICTION_API_URL,
|
||||||
|
HttpMethod.POST,
|
||||||
|
requestEntity,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use ObjectMapper to deserialize the response and automatically remove quotes
|
||||||
|
String prediction = response.getBody();
|
||||||
|
try {
|
||||||
|
prediction = objectMapper.readValue(prediction, String.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to parse prediction response", e);
|
||||||
|
prediction = "Unknown";
|
||||||
|
}
|
||||||
|
logger.info("Prediction response: {}", prediction);
|
||||||
|
|
||||||
|
// Return the prediction
|
||||||
|
return prediction;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/java/com/safeqr/app/qrcode/.DS_Store
vendored
BIN
src/main/java/com/safeqr/app/qrcode/.DS_Store
vendored
Binary file not shown.
@@ -1,22 +1,28 @@
|
|||||||
package com.safeqr.app.qrcode.controller;
|
package com.safeqr.app.qrcode.controller;
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import static com.safeqr.app.constants.APIConstants.*;
|
||||||
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
import com.safeqr.app.qrcode.dto.response.BaseScanResponse;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||||
import com.safeqr.app.qrcode.service.QRCodeTypeService;
|
import com.safeqr.app.qrcode.service.QRCodeTypeService;
|
||||||
import com.safeqr.app.qrcode.service.RedirectCountService;
|
|
||||||
import com.safeqr.app.qrcode.service.URLVerificationService;
|
import com.safeqr.app.qrcode.service.URLVerificationService;
|
||||||
import com.safeqr.app.qrcode.service.VirusTotalService;
|
import com.safeqr.app.qrcode.service.VirusTotalService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/v1/api/qrcodetypes")
|
@RequestMapping(API_VERSION)
|
||||||
public class QRCodeTypeController {
|
public class QRCodeTypeController {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(QRCodeTypeController.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private QRCodeTypeService qrCodeTypeService;
|
private QRCodeTypeService qrCodeTypeService;
|
||||||
@@ -27,26 +33,32 @@ public class QRCodeTypeController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private VirusTotalService virusTotalService;
|
private VirusTotalService virusTotalService;
|
||||||
|
|
||||||
@Autowired
|
@GetMapping(value = API_URL_QRCODE_GET_ALL)
|
||||||
private RedirectCountService redirectCountService;
|
public ResponseEntity<List<QRCodeTypeEntity>> getAllTypes() {
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public ResponseEntity<List<QRCodeType>> getAllTypes() {
|
|
||||||
return ResponseEntity.ok(qrCodeTypeService.getAllTypes());
|
return ResponseEntity.ok(qrCodeTypeService.getAllTypes());
|
||||||
}
|
}
|
||||||
|
@GetMapping(value = API_URL_QRCODE_GET_QR_DETAILS)
|
||||||
@PostMapping("/detect")
|
public ResponseEntity<BaseScanResponse> getScannedQRCodeDetails(@RequestHeader(name="QR-ID") UUID qrCodeId) {
|
||||||
public ResponseEntity<String> detectType(@RequestBody QRCodePayload payload) {
|
logger.info("Invoking GET QRCode details endpoint, qrCodeId: {}", qrCodeId);
|
||||||
return ResponseEntity.ok(qrCodeTypeService.detectType(payload).block());
|
return ResponseEntity.ok(qrCodeTypeService.getScannedQRCodeDetails(qrCodeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/verifyURL")
|
@PostMapping(value = API_URL_QRCODE_SCAN, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public ResponseEntity<URLVerificationResponse> verifyURL(@RequestBody QRCodePayload payload) {
|
public ResponseEntity<BaseScanResponse> scanQRCode(@RequestBody QRCodePayload payload,
|
||||||
URLVerificationResponse response = urlVerificationService.verifyURL(payload);
|
@RequestHeader(required = false, name = HEADER_USER_ID) String userId) {
|
||||||
return ResponseEntity.ok(response);
|
logger.info("User Id Invoking scan endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/virusTotalCheck")
|
@PostMapping(value = API_URL_QRCODE_VERIFY_URL, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseScanResponse> verifyURL(@RequestBody QRCodePayload payload,
|
||||||
|
@RequestHeader(required = false, name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking verify url endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(API_URL_QRCODE_VIRUS_TOTAL_CHECK)
|
||||||
public ResponseEntity<Boolean> virusTotalCheck(@RequestBody QRCodePayload payload) {
|
public ResponseEntity<Boolean> virusTotalCheck(@RequestBody QRCodePayload payload) {
|
||||||
try {
|
try {
|
||||||
String analysisId = virusTotalService.scanURL(payload);
|
String analysisId = virusTotalService.scanURL(payload);
|
||||||
@@ -57,9 +69,5 @@ public class QRCodeTypeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/checkRedirects")
|
|
||||||
public ResponseEntity<RedirectCountResponse> checkRedirects(@RequestBody QRCodePayload payload) {
|
|
||||||
return ResponseEntity.ok(redirectCountService.countRedirects(payload).block());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.safeqr.app.qrcode.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class RedirectCountResponse {
|
|
||||||
private int redirectCount;
|
|
||||||
private String message;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.safeqr.app.qrcode.dto;
|
package com.safeqr.app.qrcode.dto.request;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.safeqr.app.qrcode.dto.response;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@SuperBuilder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BaseScanResponse {
|
||||||
|
private QRCodeModel qrcode;
|
||||||
|
}
|
||||||
35
src/main/java/com/safeqr/app/qrcode/entity/EmailEntity.java
Normal file
35
src/main/java/com/safeqr/app/qrcode/entity/EmailEntity.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "email", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class EmailEntity {
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
private String title;
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
38
src/main/java/com/safeqr/app/qrcode/entity/PhoneEntity.java
Normal file
38
src/main/java/com/safeqr/app/qrcode/entity/PhoneEntity.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "phone", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PhoneEntity {
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "remarks")
|
||||||
|
private String remarks;
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
package com.safeqr.app.qrcode.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "qr_code", schema = "safeqr")
|
|
||||||
@Data
|
|
||||||
public class QRCode {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(generator = "UUID")
|
|
||||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
|
||||||
@Column(updatable = false, nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "qr_code_type_id", nullable = false)
|
|
||||||
private QRCodeType qrCodeType;
|
|
||||||
|
|
||||||
private String userId;
|
|
||||||
private String contents;
|
|
||||||
|
|
||||||
@Column(name = "created_at", insertable = false, updatable = false)
|
|
||||||
private String createdAt;
|
|
||||||
}
|
|
||||||
46
src/main/java/com/safeqr/app/qrcode/entity/QRCodeEntity.java
Normal file
46
src/main/java/com/safeqr/app/qrcode/entity/QRCodeEntity.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.CLASSIFY_UNKNOWN;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "qr_code", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class QRCodeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(generator = "UUID")
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String userId;
|
||||||
|
private String contents;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
@JoinColumn(name = "qr_code_type_id", nullable = false)
|
||||||
|
private QRCodeTypeEntity info;
|
||||||
|
|
||||||
|
@Column(name = "created_at", insertable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "result_category")
|
||||||
|
private String result = CLASSIFY_UNKNOWN;
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
|
|
||||||
package com.safeqr.app.qrcode.entity;
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "qr_code_types", schema = "safeqr")
|
@Table(name = "qr_code_types", schema = "safeqr")
|
||||||
@Data
|
@Data
|
||||||
public class QRCodeType {
|
public class QRCodeTypeEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
private String prefix;
|
private String prefix;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
private String tableName;
|
private String tableName;
|
||||||
}
|
}
|
||||||
39
src/main/java/com/safeqr/app/qrcode/entity/SMSEntity.java
Normal file
39
src/main/java/com/safeqr/app/qrcode/entity/SMSEntity.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "sms", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SMSEntity {
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@Column(name = "keyword_detected")
|
||||||
|
private String keywordDetected;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Table(name = "scan_bookmark", schema = "safeqr")
|
||||||
|
public class ScanBookmarkEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "qr_code_id", nullable = false)
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "status")
|
||||||
|
private BookmarkStatus bookmarkStatus;
|
||||||
|
|
||||||
|
@Column(name = "date_created", updatable = false)
|
||||||
|
private OffsetDateTime dateCreated;
|
||||||
|
|
||||||
|
@Column(name = "date_updated")
|
||||||
|
private OffsetDateTime dateUpdated;
|
||||||
|
|
||||||
|
public enum BookmarkStatus {
|
||||||
|
ACTIVE,
|
||||||
|
INACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "qr_code_id", referencedColumnName = "id", insertable = false, updatable = false)
|
||||||
|
private QRCodeEntity qrCodeEntity;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
public void prePersist() {
|
||||||
|
OffsetDateTime now = OffsetDateTime.now();
|
||||||
|
dateCreated = now;
|
||||||
|
dateUpdated = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Table(name = "scan_history", schema = "safeqr")
|
||||||
|
public class ScanHistoryEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "qr_code_id", nullable = false)
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "status")
|
||||||
|
private ScanStatus scanStatus;
|
||||||
|
|
||||||
|
@Column(name = "date_created", updatable = false)
|
||||||
|
private OffsetDateTime dateCreated;
|
||||||
|
|
||||||
|
@Column(name = "date_updated")
|
||||||
|
private OffsetDateTime dateUpdated;
|
||||||
|
|
||||||
|
@Column(name = "bookmarked")
|
||||||
|
private boolean bookmarked;
|
||||||
|
|
||||||
|
public enum ScanStatus {
|
||||||
|
ACTIVE,
|
||||||
|
INACTIVE
|
||||||
|
}
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "qr_code_id", referencedColumnName = "id", insertable = false, updatable = false)
|
||||||
|
private QRCodeEntity qrCodeEntity;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
public void prePersist() {
|
||||||
|
OffsetDateTime now = OffsetDateTime.now();
|
||||||
|
dateCreated = now;
|
||||||
|
dateUpdated = now;
|
||||||
|
}
|
||||||
|
@PreUpdate
|
||||||
|
public void preUpdate() {
|
||||||
|
dateUpdated = OffsetDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
src/main/java/com/safeqr/app/qrcode/entity/TextEntity.java
Normal file
33
src/main/java/com/safeqr/app/qrcode/entity/TextEntity.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "text", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TextEntity {
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
}
|
||||||
125
src/main/java/com/safeqr/app/qrcode/entity/URLEntity.java
Normal file
125
src/main/java/com/safeqr/app/qrcode/entity/URLEntity.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "url", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class URLEntity {
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name="classifications")
|
||||||
|
private String classifications;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(generator = "UUID")
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private String subdomain;
|
||||||
|
|
||||||
|
private String topLevelDomain;
|
||||||
|
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private String fragment;
|
||||||
|
|
||||||
|
private int redirect = 0;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Type(ListArrayType.class)
|
||||||
|
@Column(name = "hsts_header", columnDefinition = "text[]")
|
||||||
|
private List<String> hstsHeader = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Type(ListArrayType.class)
|
||||||
|
@Column(name = "ssl_stripping", columnDefinition = "boolean[]")
|
||||||
|
private List<Boolean> sslStripping = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Type(ListArrayType.class)
|
||||||
|
@Column(name = "redirect_chain", columnDefinition = "text[]")
|
||||||
|
private List<String> redirectChain = new ArrayList<>();
|
||||||
|
|
||||||
|
@Column(name = "hostname_embedding")
|
||||||
|
private Integer hostnameEmbedding = 0;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "javascript_check")
|
||||||
|
private String javascriptCheck = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "shortening_service")
|
||||||
|
private String shorteningService = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "has_ip_address")
|
||||||
|
private String hasIpAddress = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Type(ListArrayType.class)
|
||||||
|
@Column(name = "tracking_descriptions", columnDefinition = "text[]")
|
||||||
|
private List<String> trackingDescriptions = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "url_encoding")
|
||||||
|
private String urlEncoding = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name="has_executable")
|
||||||
|
private String hasExecutable = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name = "dns_error")
|
||||||
|
private String dnsError = "";
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@Column(name="ssl_error")
|
||||||
|
private String sslError = "";
|
||||||
|
|
||||||
|
// Custom getter for hostnameEmbedding
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public Integer getHostnameEmbedding() {
|
||||||
|
return hostnameEmbedding == 0 ? null : hostnameEmbedding;
|
||||||
|
}
|
||||||
|
// Custom getter for path
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public String getPath() {
|
||||||
|
return path == null || path.isEmpty() ? null : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom getter for query
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@JsonProperty
|
||||||
|
public String getQuery() {
|
||||||
|
return query == null || query.equals("{}") ? null : query;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/main/java/com/safeqr/app/qrcode/entity/WifiEntity.java
Normal file
38
src/main/java/com/safeqr/app/qrcode/entity/WifiEntity.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.UuidGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "wifi", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WifiEntity {
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@UuidGenerator
|
||||||
|
@Column(updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "qr_code_id")
|
||||||
|
private UUID qrCodeId;
|
||||||
|
|
||||||
|
private String ssid;
|
||||||
|
private String password;
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private String encryption;
|
||||||
|
private boolean hidden;
|
||||||
|
}
|
||||||
44
src/main/java/com/safeqr/app/qrcode/model/EmailModel.java
Normal file
44
src/main/java/com/safeqr/app/qrcode/model/EmailModel.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package com.safeqr.app.qrcode.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.EmailEntity;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.service.EmailVerificationService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class EmailModel extends QRCodeModel<EmailEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EmailModel.class);
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final EmailVerificationService emailVerificationService;
|
||||||
|
|
||||||
|
public EmailModel(QRCodeEntity scannedQRCodeEntity, EmailVerificationService emailVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.emailVerificationService = emailVerificationService;
|
||||||
|
this.details = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
details = EmailEntity.builder().qrCodeId(data.getId()).build();
|
||||||
|
|
||||||
|
emailVerificationService.parseEmailString(details, data.getContents());
|
||||||
|
// Insert into email table
|
||||||
|
emailVerificationService.insertDB(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmailEntity getDetails () {
|
||||||
|
return emailVerificationService.getEmailEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/main/java/com/safeqr/app/qrcode/model/PhoneModel.java
Normal file
43
src/main/java/com/safeqr/app/qrcode/model/PhoneModel.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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.PhoneEntity;
|
||||||
|
import com.safeqr.app.qrcode.service.PhoneVerificationService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class PhoneModel extends QRCodeModel<PhoneEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PhoneModel.class);
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final PhoneVerificationService phoneVerificationService;
|
||||||
|
|
||||||
|
public PhoneModel(QRCodeEntity scannedQRCodeEntity, PhoneVerificationService phoneVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.phoneVerificationService = phoneVerificationService;
|
||||||
|
this.details = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
details = PhoneEntity.builder().qrCodeId(data.getId()).build();
|
||||||
|
|
||||||
|
phoneVerificationService.parsePhoneString(details, data.getContents());
|
||||||
|
// Insert into phone table
|
||||||
|
phoneVerificationService.insertDB(details);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public PhoneEntity getDetails () {
|
||||||
|
return phoneVerificationService.getPhoneEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return phoneVerificationService.checkPhoneNumber(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/com/safeqr/app/qrcode/model/QRCodeModel.java
Normal file
14
src/main/java/com/safeqr/app/qrcode/model/QRCodeModel.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.safeqr.app.qrcode.model;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public abstract class QRCodeModel<T>{
|
||||||
|
QRCodeEntity data;
|
||||||
|
T details;
|
||||||
|
|
||||||
|
public abstract void setDetails();
|
||||||
|
public abstract T getDetails();
|
||||||
|
public abstract String retrieveClassification();
|
||||||
|
}
|
||||||
43
src/main/java/com/safeqr/app/qrcode/model/SMSModel.java
Normal file
43
src/main/java/com/safeqr/app/qrcode/model/SMSModel.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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.SMSEntity;
|
||||||
|
import com.safeqr.app.qrcode.service.SMSVerificationService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class SMSModel extends QRCodeModel<SMSEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SMSModel.class);
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final SMSVerificationService smsVerificationService;
|
||||||
|
|
||||||
|
public SMSModel(QRCodeEntity scannedQRCodeEntity, SMSVerificationService smsVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.smsVerificationService = smsVerificationService;
|
||||||
|
this.details = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
details = SMSEntity.builder().qrCodeId(data.getId()).build();
|
||||||
|
|
||||||
|
smsVerificationService.parseSMSString(details, data.getContents());
|
||||||
|
// Insert into sms table
|
||||||
|
smsVerificationService.insertDB(details);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public SMSEntity getDetails () {
|
||||||
|
return smsVerificationService.getSMSEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return smsVerificationService.getClassification(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/com/safeqr/app/qrcode/model/TextModel.java
Normal file
42
src/main/java/com/safeqr/app/qrcode/model/TextModel.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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.TextEntity;
|
||||||
|
import com.safeqr.app.qrcode.service.TextVerificationService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class TextModel extends QRCodeModel<TextEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(TextModel.class);
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final TextVerificationService textVerificationService;
|
||||||
|
|
||||||
|
public TextModel(QRCodeEntity scannedQRCodeEntity, TextVerificationService textVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.textVerificationService = textVerificationService;
|
||||||
|
this.details = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
details = TextEntity.builder().qrCodeId(data.getId()).text(data.getContents()).build();
|
||||||
|
// Insert into text table
|
||||||
|
textVerificationService.insertDB(details);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public TextEntity getDetails () {
|
||||||
|
return textVerificationService.getTextEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/com/safeqr/app/qrcode/model/URLModel.java
Normal file
52
src/main/java/com/safeqr/app/qrcode/model/URLModel.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package com.safeqr.app.qrcode.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||||
|
import com.safeqr.app.qrcode.service.URLVerificationService;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import lombok.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class URLModel extends QRCodeModel<URLEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(URLModel.class);
|
||||||
|
@JsonIgnore
|
||||||
|
private final URLVerificationService urlVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public URLModel(QRCodeEntity scannedQRCodeEntity, URLVerificationService urlVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.urlVerificationService = urlVerificationService;
|
||||||
|
details = null;
|
||||||
|
}
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
String url = data.getContents();
|
||||||
|
try {
|
||||||
|
details = urlVerificationService.breakdownURL(url);
|
||||||
|
urlVerificationService.countAndTrackRedirects(url, details);
|
||||||
|
// set qrCode Identifier
|
||||||
|
details.setQrCodeId(data.getId());
|
||||||
|
// Insert into URL table
|
||||||
|
urlVerificationService.insertDB(details);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public URLEntity getDetails () {
|
||||||
|
return urlVerificationService.getURLEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return urlVerificationService.getClassification(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/com/safeqr/app/qrcode/model/WifiModel.java
Normal file
45
src/main/java/com/safeqr/app/qrcode/model/WifiModel.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package com.safeqr.app.qrcode.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.WifiEntity;
|
||||||
|
import com.safeqr.app.qrcode.service.WifiVerificationService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public final class WifiModel extends QRCodeModel<WifiEntity> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(WifiModel.class);
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final WifiVerificationService wifiVerificationService;
|
||||||
|
|
||||||
|
public WifiModel(QRCodeEntity scannedQRCodeEntity, WifiVerificationService wifiVerificationService) {
|
||||||
|
this.data = scannedQRCodeEntity;
|
||||||
|
this.wifiVerificationService = wifiVerificationService;
|
||||||
|
this.details = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDetails() {
|
||||||
|
details = WifiEntity.builder().qrCodeId(data.getId()).build();
|
||||||
|
|
||||||
|
// Parse wifi string
|
||||||
|
wifiVerificationService.parseWifiString(details, data.getContents());
|
||||||
|
|
||||||
|
// Insert into wifi table
|
||||||
|
wifiVerificationService.insertDB(details);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public WifiEntity getDetails () {
|
||||||
|
return wifiVerificationService.getWifiEntityByQRCodeId(data.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String retrieveClassification() {
|
||||||
|
return wifiVerificationService.getClassification(details.getEncryption());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.EmailModel;
|
||||||
|
import com.safeqr.app.qrcode.service.EmailVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class EmailFactory implements QRCodeFactory<EmailModel> {
|
||||||
|
private final EmailVerificationService emailVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EmailFactory(EmailVerificationService emailVerificationService) {
|
||||||
|
this.emailVerificationService = emailVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmailModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new EmailModel(scannedQRCodeEntity, emailVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.PhoneModel;
|
||||||
|
import com.safeqr.app.qrcode.service.PhoneVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PhoneFactory implements QRCodeFactory<PhoneModel> {
|
||||||
|
private final PhoneVerificationService phoneVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PhoneFactory(PhoneVerificationService phoneVerificationService) {
|
||||||
|
this.phoneVerificationService = phoneVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhoneModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new PhoneModel(scannedQRCodeEntity, phoneVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface QRCodeFactory<T extends QRCodeModel<?>> {
|
||||||
|
T create(QRCodeEntity scannedQRCodeEntity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class QRCodeFactoryProvider {
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
@Autowired
|
||||||
|
public QRCodeFactoryProvider(ApplicationContext applicationContext) {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QRCodeModel createQRCodeInstance(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return switch (scannedQRCodeEntity.getInfo().getType().toUpperCase()) {
|
||||||
|
case QR_CODE_TYPE_URL-> applicationContext.getBean(URLFactory.class).create(scannedQRCodeEntity);
|
||||||
|
case QR_CODE_TYPE_PHONE -> applicationContext.getBean(PhoneFactory.class).create(scannedQRCodeEntity);
|
||||||
|
case QR_CODE_TYPE_SMS -> applicationContext.getBean(SMSFactory.class).create(scannedQRCodeEntity);
|
||||||
|
case QR_CODE_TYPE_EMAIL -> applicationContext.getBean(EmailFactory.class).create(scannedQRCodeEntity);
|
||||||
|
case QR_CODE_TYPE_WIFI -> applicationContext.getBean(WifiFactory.class).create(scannedQRCodeEntity);
|
||||||
|
case DEFAULT_QR_CODE_TYPE -> applicationContext.getBean(TextFactory.class).create(scannedQRCodeEntity);
|
||||||
|
//default -> throw new IllegalArgumentException("Unsupported QR code type: " + scannedQRCodeEntity.getInfo().getType());
|
||||||
|
default -> applicationContext.getBean(TextFactory.class).create(scannedQRCodeEntity);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.SMSModel;
|
||||||
|
import com.safeqr.app.qrcode.service.SMSVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SMSFactory implements QRCodeFactory<SMSModel> {
|
||||||
|
private final SMSVerificationService smsVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SMSFactory(SMSVerificationService smsVerificationService) {
|
||||||
|
this.smsVerificationService = smsVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SMSModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new SMSModel(scannedQRCodeEntity, smsVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.TextModel;
|
||||||
|
import com.safeqr.app.qrcode.service.TextVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TextFactory implements QRCodeFactory<TextModel> {
|
||||||
|
private final TextVerificationService textVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TextFactory(TextVerificationService textVerificationService) {
|
||||||
|
this.textVerificationService = textVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new TextModel(scannedQRCodeEntity, textVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.URLModel;
|
||||||
|
import com.safeqr.app.qrcode.service.URLVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class URLFactory implements QRCodeFactory<URLModel> {
|
||||||
|
private final URLVerificationService urlVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public URLFactory(URLVerificationService urlVerificationService) {
|
||||||
|
this.urlVerificationService = urlVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URLModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new URLModel(scannedQRCodeEntity, urlVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.safeqr.app.qrcode.model.factory;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.WifiModel;
|
||||||
|
import com.safeqr.app.qrcode.service.WifiVerificationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WifiFactory implements QRCodeFactory<WifiModel> {
|
||||||
|
private final WifiVerificationService wifiVerificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WifiFactory(WifiVerificationService wifiVerificationService) {
|
||||||
|
this.wifiVerificationService = wifiVerificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WifiModel create(QRCodeEntity scannedQRCodeEntity) {
|
||||||
|
return new WifiModel(scannedQRCodeEntity, wifiVerificationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.EmailEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface EmailRepository extends GenericRepository<EmailEntity> {
|
||||||
|
Optional<EmailEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.repository.NoRepositoryBean;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoRepositoryBean
|
||||||
|
public interface GenericRepository<T> extends JpaRepository<T, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.PhoneEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface PhoneRepository extends GenericRepository<PhoneEntity> {
|
||||||
|
Optional<PhoneEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
|
||||||
|
public interface QRCodeRepository extends GenericRepository<QRCodeEntity> {
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
package com.safeqr.app.qrcode.repository;
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface QRCodeTypeRepository extends JpaRepository<QRCodeType, Long> {
|
public interface QRCodeTypeRepository extends JpaRepository<QRCodeTypeEntity, Long> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.SMSEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface SMSRepository extends GenericRepository<SMSEntity> {
|
||||||
|
Optional<SMSEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.ScanBookmarkEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface ScanBookmarkRepository extends JpaRepository<ScanBookmarkEntity, Long> {
|
||||||
|
@Query("SELECT sb.qrCodeEntity FROM ScanBookmarkEntity sb WHERE sb.userId = :userId AND sb.bookmarkStatus = 'ACTIVE'")
|
||||||
|
List<QRCodeEntity> findAllBookmarksByUserId(String userId);
|
||||||
|
|
||||||
|
@Query("SELECT sb FROM ScanBookmarkEntity sb WHERE sb.userId = :userId AND sb.qrCodeId = :qrCodeId AND sb.bookmarkStatus = 'ACTIVE'")
|
||||||
|
Optional<ScanBookmarkEntity> findByUserIdAndQrCodeId(String userId, UUID qrCodeId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE ScanBookmarkEntity sb SET sb.bookmarkStatus = com.safeqr.app.qrcode.entity.ScanBookmarkEntity$BookmarkStatus.INACTIVE, sb.dateUpdated = CURRENT_TIMESTAMP WHERE sb.userId = :userId AND sb.bookmarkStatus = com.safeqr.app.qrcode.entity.ScanBookmarkEntity$BookmarkStatus.ACTIVE AND sb.qrCodeId = :qrCodeId")
|
||||||
|
int updateBookmarkStatusToInactive(String userId, UUID qrCodeId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE ScanBookmarkEntity sb SET sb.bookmarkStatus = com.safeqr.app.qrcode.entity.ScanBookmarkEntity$BookmarkStatus.INACTIVE, sb.dateUpdated = CURRENT_TIMESTAMP WHERE sb.userId = :userId AND sb.bookmarkStatus = com.safeqr.app.qrcode.entity.ScanBookmarkEntity$BookmarkStatus.ACTIVE")
|
||||||
|
int updateBookmarkStatusToInactiveByUserId(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.ScanBookmarkEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.ScanHistoryEntity;
|
||||||
|
import com.safeqr.app.user.dto.ScannedHistoriesDto;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ScanHistoryRepository extends JpaRepository<ScanHistoryEntity, Long> {
|
||||||
|
@Query("SELECT new com.safeqr.app.user.dto.ScannedHistoriesDto(sh.qrCodeEntity, sh.bookmarked) FROM ScanHistoryEntity sh WHERE sh.userId = :userId AND sh.scanStatus = 'ACTIVE' ORDER BY sh.dateCreated DESC")
|
||||||
|
List<ScannedHistoriesDto> findAllQRCodesByUserId(@Param("userId") String userId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE ScanHistoryEntity sh SET sh.scanStatus = com.safeqr.app.qrcode.entity.ScanHistoryEntity$ScanStatus.INACTIVE, sh.dateUpdated = CURRENT_TIMESTAMP WHERE sh.userId = :userId AND sh.scanStatus = com.safeqr.app.qrcode.entity.ScanHistoryEntity$ScanStatus.ACTIVE AND sh.qrCodeId = :qrCodeId")
|
||||||
|
int updateScannedHistoryToInactive(String userId, UUID qrCodeId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE ScanHistoryEntity sh SET sh.scanStatus = com.safeqr.app.qrcode.entity.ScanHistoryEntity$ScanStatus.INACTIVE, sh.dateUpdated = CURRENT_TIMESTAMP WHERE sh.userId = :userId AND sh.scanStatus = com.safeqr.app.qrcode.entity.ScanHistoryEntity$ScanStatus.ACTIVE")
|
||||||
|
int updateScannedHistoriesToInactiveByUserId(String userId);
|
||||||
|
@Query("SELECT sh FROM ScanHistoryEntity sh WHERE sh.userId = :userId AND sh.qrCodeId = :qrCodeId AND sh.bookmarked = true AND sh.scanStatus = 'ACTIVE'")
|
||||||
|
Optional<ScanHistoryEntity> findByUserIdAndQrCodeId(String userId, UUID qrCodeId);
|
||||||
|
|
||||||
|
@Query("SELECT new com.safeqr.app.user.dto.ScannedHistoriesDto(sh.qrCodeEntity, sh.bookmarked) FROM ScanHistoryEntity sh WHERE sh.userId = :userId AND sh.bookmarked = true AND sh.scanStatus = 'ACTIVE' ORDER BY sh.dateCreated DESC")
|
||||||
|
List<ScannedHistoriesDto> findAllBookmarksByUserId(@Param("userId") String userId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE ScanHistoryEntity sh SET sh.bookmarked = true, sh.dateUpdated = CURRENT_TIMESTAMP " +
|
||||||
|
"WHERE sh.userId = :userId AND sh.bookmarked = false AND sh.qrCodeId = :qrCodeId AND sh.scanStatus = 'ACTIVE'")
|
||||||
|
int updateBookmarkStatusToActive(String userId, UUID qrCodeId);
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE ScanHistoryEntity sh SET sh.bookmarked = false, sh.dateUpdated = CURRENT_TIMESTAMP " +
|
||||||
|
"WHERE sh.userId = :userId AND sh.bookmarked = true AND sh.qrCodeId = :qrCodeId AND sh.scanStatus = 'ACTIVE'")
|
||||||
|
int updateBookmarkStatusToInactive(String userId, UUID qrCodeId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Transactional
|
||||||
|
@Query("UPDATE ScanHistoryEntity sh SET sh.bookmarked = false, sh.dateUpdated = CURRENT_TIMESTAMP WHERE sh.userId = :userId AND sh.bookmarked = true AND sh.scanStatus = 'ACTIVE'")
|
||||||
|
int updateBookmarkStatusToInactiveByUserId(String userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.TextEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface TextRepository extends GenericRepository<TextEntity> {
|
||||||
|
Optional<TextEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface URLRepository extends GenericRepository<URLEntity> {
|
||||||
|
@Transactional
|
||||||
|
Optional<URLEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.WifiEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface WifiRepository extends GenericRepository<WifiEntity> {
|
||||||
|
Optional<WifiEntity> findByQrCodeId(UUID qrCodeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.entity.EmailEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.EmailRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmailVerificationService {
|
||||||
|
private final EmailRepository emailRepository;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EmailVerificationService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EmailVerificationService(EmailRepository emailRepository) {
|
||||||
|
this.emailRepository = emailRepository;
|
||||||
|
}
|
||||||
|
public EmailEntity getEmailEntityByQRCodeId(UUID qrCodeId) {
|
||||||
|
logger.info("qrCodeId retrieving: {}", qrCodeId);
|
||||||
|
return emailRepository.findByQrCodeId(qrCodeId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("Email not found for QR Code id: " + qrCodeId));
|
||||||
|
}
|
||||||
|
public void insertDB(EmailEntity emailEntity) {
|
||||||
|
emailRepository.save(emailEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parseEmailString(EmailEntity emailEntity, String emailString) {
|
||||||
|
Optional.ofNullable(emailString)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.filter(s -> s.startsWith("MAILTO:"))
|
||||||
|
.map(s -> s.substring(7))
|
||||||
|
.map(s -> s.split("\\?", 2))
|
||||||
|
.filter(parts -> parts.length > 0)
|
||||||
|
.ifPresentOrElse(
|
||||||
|
parts -> {
|
||||||
|
String email = parts[0];
|
||||||
|
Map<String, String> params = (parts.length == 2)
|
||||||
|
? Arrays.stream(parts[1].split("&"))
|
||||||
|
.map(param -> param.split("=", 2))
|
||||||
|
.filter(keyValue -> keyValue.length == 2)
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
keyValue -> keyValue[0],
|
||||||
|
keyValue -> keyValue[1],
|
||||||
|
(v1, v2) -> v1
|
||||||
|
))
|
||||||
|
: Map.of();
|
||||||
|
|
||||||
|
emailEntity.setEmail(email);
|
||||||
|
emailEntity.setTitle(params.getOrDefault("subject", ""));
|
||||||
|
emailEntity.setMessage(params.getOrDefault("body", ""));
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
throw new InvalidFormatExceptions("Invalid email format. Expected format: MAILTO:<email>?subject=<title>&body=<message>");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.entity.PhoneEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.PhoneRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PhoneVerificationService {
|
||||||
|
private final PhoneRepository phoneRepository;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PhoneVerificationService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PhoneVerificationService(PhoneRepository phoneRepository) {
|
||||||
|
this.phoneRepository = phoneRepository;
|
||||||
|
}
|
||||||
|
public PhoneEntity getPhoneEntityByQRCodeId(UUID qrCodeId) {
|
||||||
|
logger.info("qrCodeId retrieving: {}", qrCodeId);
|
||||||
|
return phoneRepository.findByQrCodeId(qrCodeId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("Phone not found for QR Code id: " + qrCodeId));
|
||||||
|
}
|
||||||
|
public void insertDB(PhoneEntity phoneEntity) {
|
||||||
|
phoneRepository.save(phoneEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parsePhoneString(PhoneEntity phoneEntity, String phoneString) {
|
||||||
|
// Validate the string format
|
||||||
|
if (phoneString == null || phoneString.isEmpty()) {
|
||||||
|
throw new InvalidFormatExceptions("Phone string cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the "TEL:" prefix
|
||||||
|
String phoneNumber = phoneString.substring(4);
|
||||||
|
|
||||||
|
// Further validation for phone number can be done here (optional)
|
||||||
|
if (phoneNumber.matches("\\+?[0-9]*")) {
|
||||||
|
// Populate the PhoneEntity object
|
||||||
|
phoneEntity.setPhone(phoneNumber);
|
||||||
|
} else {
|
||||||
|
throw new InvalidFormatExceptions("Invalid phone number format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String checkPhoneNumber(PhoneEntity phoneEntity) {
|
||||||
|
// Remove any spaces, dashes, parentheses, and trim the ends
|
||||||
|
String phoneNumber = phoneEntity.getPhone().replaceAll("[\\s\\-()]", "").trim();
|
||||||
|
|
||||||
|
// Check if the number starts with +65 or just 65
|
||||||
|
if (phoneNumber.startsWith("+65")) {
|
||||||
|
phoneNumber = phoneNumber.substring(3); // Remove the "+65"
|
||||||
|
} else if (phoneNumber.startsWith("65")) {
|
||||||
|
phoneNumber = phoneNumber.substring(2); // Remove the "65"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a valid Singapore mobile or landline number
|
||||||
|
if (phoneNumber.matches("^[689]\\d{7}$")) {
|
||||||
|
if (phoneNumber.startsWith("8") || phoneNumber.startsWith("9")) {
|
||||||
|
phoneEntity.setRemarks("Singapore mobile number - This number has not been scanned for scam. Please do not divulge your personal information.");
|
||||||
|
} else if (phoneNumber.startsWith("6")) {
|
||||||
|
phoneEntity.setRemarks("Singapore landline number - This phone number has not been scanned for scam. Please do not divulge your personal information.");
|
||||||
|
}
|
||||||
|
return CLASSIFY_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it doesn't match mobile or landline pattern
|
||||||
|
phoneEntity.setRemarks("Warning: This is either an overseas number or an invalid Singapore number. Please exercise caution.");
|
||||||
|
return CLASSIFY_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +1,146 @@
|
|||||||
|
|
||||||
package com.safeqr.app.qrcode.service;
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||||
|
import com.safeqr.app.qrcode.dto.response.BaseScanResponse;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.ScanHistoryEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.factory.QRCodeFactoryProvider;
|
||||||
|
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||||
|
import com.safeqr.app.qrcode.repository.QRCodeRepository;
|
||||||
import com.safeqr.app.qrcode.repository.QRCodeTypeRepository;
|
import com.safeqr.app.qrcode.repository.QRCodeTypeRepository;
|
||||||
|
import com.safeqr.app.qrcode.repository.ScanHistoryRepository;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class QRCodeTypeService {
|
public class QRCodeTypeService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(QRCodeTypeService.class);
|
||||||
|
|
||||||
|
private final QRCodeFactoryProvider qrCodeFactoryProvider;
|
||||||
|
private final QRCodeTypeRepository qrCodeTypeRepository;
|
||||||
|
private final ScanHistoryRepository scanHistoryRepository;
|
||||||
|
private final QRCodeRepository qrCodeRepository;
|
||||||
|
private final SafeBrowsingService safeBrowsingService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private QRCodeTypeRepository qrCodeTypeRepository;
|
public QRCodeTypeService(QRCodeFactoryProvider qrCodeFactoryProvider,
|
||||||
|
QRCodeTypeRepository qrCodeTypeRepository,
|
||||||
|
ScanHistoryRepository scanHistoryRepository,
|
||||||
|
QRCodeRepository qrCodeRepository,
|
||||||
|
SafeBrowsingService safeBrowsingService
|
||||||
|
) {
|
||||||
|
this.qrCodeFactoryProvider = qrCodeFactoryProvider;
|
||||||
|
this.qrCodeTypeRepository = qrCodeTypeRepository;
|
||||||
|
this.scanHistoryRepository = scanHistoryRepository;
|
||||||
|
this.qrCodeRepository = qrCodeRepository;
|
||||||
|
this.safeBrowsingService = safeBrowsingService;
|
||||||
|
}
|
||||||
|
private List<QRCodeTypeEntity> configs;
|
||||||
|
private QRCodeTypeEntity defaultQRCodeTypeEntity;
|
||||||
|
|
||||||
@Autowired
|
@PostConstruct
|
||||||
private SafeBrowsingService safeBrowsingService;
|
public void loadQRCodeTypes() {
|
||||||
|
// Fetch all QR Code Types from the database
|
||||||
public List<QRCodeType> getAllTypes() {
|
configs = qrCodeTypeRepository.findAll();
|
||||||
return qrCodeTypeRepository.findAll();
|
// Set the default QR Code Type
|
||||||
|
defaultQRCodeTypeEntity = configs.stream()
|
||||||
|
.filter(config -> config.getType().equals(DEFAULT_QR_CODE_TYPE))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<String> detectType(QRCodePayload payload) {
|
public List<QRCodeTypeEntity> getAllTypes() {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
// 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 qrCodeModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Scanned QR Code
|
||||||
|
@Transactional
|
||||||
|
public BaseScanResponse scanQRCode(String userId, QRCodePayload payload) {
|
||||||
String data = payload.getData();
|
String data = payload.getData();
|
||||||
List<QRCodeType> configs = qrCodeTypeRepository.findAll();
|
logger.info("scanQRCode: userId={}, data={}", userId, data);
|
||||||
|
|
||||||
for (QRCodeType config : configs) {
|
QRCodeModel<?> qrCodeModel = scanAndClassify(userId, data);
|
||||||
if (data.startsWith(config.getPrefix())) {
|
UUID qrId = qrCodeModel.getData().getId();
|
||||||
if ("URL".equals(config.getType())) {
|
|
||||||
try
|
// Insert into Scan History table if userId is not null
|
||||||
{
|
logger.info("scanQRCode: scannedQR new ID={}", qrId);
|
||||||
return safeBrowsingService.isSafeUrl(data)
|
if (userId != null) {
|
||||||
.map(isSafe -> isSafe ? "Safe URL" : "Unsafe URL");
|
scanHistoryRepository.save(ScanHistoryEntity.builder()
|
||||||
} catch (NoSuchAlgorithmException e)
|
.qrCodeId(qrId)
|
||||||
{
|
.userId(userId)
|
||||||
// TODO Auto-generated catch block
|
.scanStatus(ScanHistoryEntity.ScanStatus.ACTIVE)
|
||||||
return Mono.just("Error checking URL safety: " + e.getMessage());
|
.build());
|
||||||
}
|
|
||||||
}
|
|
||||||
return Mono.just(config.getType());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mono.just("Unknown");
|
return BaseScanResponse.builder().qrcode(qrCodeModel).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan decoded contents from email message
|
||||||
|
@Transactional
|
||||||
|
public QRCodeModel<?> scanGmailDecodedContents(String userId, String data) {
|
||||||
|
logger.info("Scan Gmail content: userId={}, data={}", userId, data);
|
||||||
|
|
||||||
|
return scanAndClassify(userId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanAndClassify
|
||||||
|
|
||||||
|
private QRCodeModel<?> scanAndClassify(String userId, String 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();
|
||||||
|
|
||||||
|
// Get classifications based on verifications
|
||||||
|
scannedQR.setResult(qrCodeModel.retrieveClassification());
|
||||||
|
|
||||||
|
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()
|
||||||
|
.filter(config -> data.toLowerCase().startsWith(config.getPrefix().toLowerCase()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(defaultQRCodeTypeEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
package com.safeqr.app.qrcode.service;
|
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
|
||||||
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RedirectCountService {
|
|
||||||
|
|
||||||
public Mono<RedirectCountResponse> countRedirects(QRCodePayload payload) {
|
|
||||||
String url = payload.getData();
|
|
||||||
|
|
||||||
return WebClient.create()
|
|
||||||
.get()
|
|
||||||
.uri(url)
|
|
||||||
.exchangeToMono(response -> {
|
|
||||||
RedirectCountResponse redirectCountResponse = new RedirectCountResponse();
|
|
||||||
redirectCountResponse.setRedirectCount(response.cookies().size());
|
|
||||||
redirectCountResponse.setMessage("Redirect count calculated.");
|
|
||||||
return Mono.just(redirectCountResponse);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.entity.PhoneEntity;
|
||||||
|
import com.safeqr.app.qrcode.entity.SMSEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.SMSRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SMSVerificationService {
|
||||||
|
private final SMSRepository smsRepository;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SMSVerificationService.class);
|
||||||
|
|
||||||
|
// Define phishing keywords categories
|
||||||
|
private static final Map<String, List<String>> PHISHING_KEYWORDS_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Generic", Arrays.asList("password", "verify", "urgent", "prize", "account update"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Tax Refund", Arrays.asList("tax refund", "claim your refund", "tax return"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Suspicious Activity", Arrays.asList("suspicious activity detected", "action required", "account compromised"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Social Media", Arrays.asList("social media account", "unauthorized login attempt", "verify your account"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Bogus Payment", Arrays.asList("payment confirmation", "transaction details", "payment receipt"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Incorrect Billing", Arrays.asList("incorrect billing information", "update billing details", "billing account"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("iCloud", Arrays.asList("icloud account", "update your icloud", "icloud security alert"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("HR Survey", Arrays.asList("human resources survey", "employee feedback", "survey participation"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Google Docs", Arrays.asList("google docs", "view shared document", "google drive"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("USPS", Arrays.asList("usps delivery", "package tracking", "shipping details"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Voicemail", Arrays.asList("voicemail notification", "missed call", "listen to voicemail"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Bogus Invoice", Arrays.asList("invoice details", "view invoice", "payment invoice"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Email Upgrade", Arrays.asList("email account upgrade", "email settings update", "upgrade your email"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Dropbox", Arrays.asList("dropbox", "view shared file", "dropbox account"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("CEO Phishing", Arrays.asList("ceo email", "urgent message from ceo", "ceo authorization"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Costco", Arrays.asList("costco", "costco membership", "costco rewards"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Bank", Arrays.asList("bank account", "unusual activity", "account login"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Fake App", Arrays.asList("app purchase", "app subscription", "confirm your purchase"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Advanced Fee", Arrays.asList("advance fee", "processing fee", "fee payment"));
|
||||||
|
PHISHING_KEYWORDS_MAP.put("Account Suspension", Arrays.asList("account suspension", "suspend your account", "account deactivation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SMSVerificationService(SMSRepository smsRepository) {
|
||||||
|
this.smsRepository = smsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SMSEntity getSMSEntityByQRCodeId(UUID qrCodeId) {
|
||||||
|
logger.info("qrCodeId retrieving: {}", qrCodeId);
|
||||||
|
return smsRepository.findByQrCodeId(qrCodeId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("SMS not found for QR Code id: " + qrCodeId));
|
||||||
|
}
|
||||||
|
public void insertDB(SMSEntity smsEntity) {
|
||||||
|
smsRepository.save(smsEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parseSMSString(SMSEntity smsEntity, String smsto) throws IllegalArgumentException{
|
||||||
|
// Validate the string format
|
||||||
|
if (smsto == null || smsto.isEmpty()) {
|
||||||
|
throw new InvalidFormatExceptions("sms cannot be null or empty.");
|
||||||
|
}
|
||||||
|
// Remove the "SMSTO:" prefix
|
||||||
|
String data = smsto.substring(6);
|
||||||
|
|
||||||
|
// Split the data into phone number and message
|
||||||
|
String[] parts = data.split(":", 2);
|
||||||
|
|
||||||
|
// If both phone number and message are available
|
||||||
|
if (parts.length == 2) {
|
||||||
|
String phone = parts[0];
|
||||||
|
String message = parts[1];
|
||||||
|
|
||||||
|
// Populate the SMSEntity object
|
||||||
|
smsEntity.setPhone(phone);
|
||||||
|
smsEntity.setMessage(message);
|
||||||
|
} else {
|
||||||
|
// Handle the case where the format is invalid
|
||||||
|
throw new InvalidFormatExceptions("Invalid SMSTO format. Expected format: SMSTO:<phone>:<message>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String getClassification (SMSEntity smsEntity) {
|
||||||
|
|
||||||
|
String lowerCaseSms = smsEntity.getMessage().toLowerCase();
|
||||||
|
logger.info("Sms: {}", lowerCaseSms);
|
||||||
|
|
||||||
|
// Iterate over the map of phishing keywords
|
||||||
|
for (Map.Entry<String, List<String>> entry : PHISHING_KEYWORDS_MAP.entrySet()) {
|
||||||
|
String category = entry.getKey();
|
||||||
|
List<String> keywords = entry.getValue();
|
||||||
|
|
||||||
|
// Check if the SMS contains any of the phishing keywords
|
||||||
|
for (String keyword : keywords) {
|
||||||
|
if (lowerCaseSms.contains(keyword)) {
|
||||||
|
logger.info("Phishing keyword detected: {}", keyword);
|
||||||
|
smsEntity.setKeywordDetected("Potential Phishing - " + category);
|
||||||
|
return checkPhoneNumber(smsEntity.getPhone()).equals(CLASSIFY_WARNING) ?
|
||||||
|
CLASSIFY_WARNING :
|
||||||
|
CLASSIFY_UNSAFE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no phishing keywords are found, sent for local phone number checks
|
||||||
|
return checkPhoneNumber(smsEntity.getPhone()).equals(CLASSIFY_UNSAFE) ?
|
||||||
|
CLASSIFY_WARNING :
|
||||||
|
CLASSIFY_SAFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkPhoneNumber(String phoneNumber) {
|
||||||
|
// Remove any spaces, dashes, parentheses, and trim the ends
|
||||||
|
phoneNumber = phoneNumber.replaceAll("[\\s\\-()]", "").trim();
|
||||||
|
|
||||||
|
// Check if the number starts with +65 or just 65
|
||||||
|
if (phoneNumber.startsWith("+65")) {
|
||||||
|
phoneNumber = phoneNumber.substring(3); // Remove the "+65"
|
||||||
|
} else if (phoneNumber.startsWith("65")) {
|
||||||
|
phoneNumber = phoneNumber.substring(2); // Remove the "65"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a valid Singapore mobile or landline number
|
||||||
|
if (phoneNumber.matches("^[689]\\d{7}$") && (phoneNumber.startsWith("8") || phoneNumber.startsWith("9"))) {
|
||||||
|
return CLASSIFY_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it doesn't match mobile
|
||||||
|
return CLASSIFY_UNSAFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.entity.TextEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.TextRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TextVerificationService {
|
||||||
|
private final TextRepository textRepository;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(TextVerificationService.class);
|
||||||
|
@Autowired
|
||||||
|
public TextVerificationService(TextRepository textRepository) {
|
||||||
|
this.textRepository = textRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextEntity getTextEntityByQRCodeId(UUID qrCodeId) {
|
||||||
|
logger.info("qrCodeId retrieving: {}", qrCodeId);
|
||||||
|
return textRepository.findByQrCodeId(qrCodeId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("Text not found for QR Code id: " + qrCodeId));
|
||||||
|
}
|
||||||
|
public void insertDB(TextEntity textEntity) {
|
||||||
|
textRepository.save(textEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +1,474 @@
|
|||||||
package com.safeqr.app.qrcode.service;
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||||
|
import com.safeqr.app.qrcode.model.URLModel;
|
||||||
|
import com.safeqr.app.qrcode.repository.URLRepository;
|
||||||
|
import com.safeqr.app.prediction.service.PredictionService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class URLVerificationService {
|
public class URLVerificationService {
|
||||||
|
private static final int CONNECTION_TIMEOUT_MS = 10000;
|
||||||
|
private static final int READ_TIMEOUT_MS = 10000;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(URLVerificationService.class);
|
||||||
|
private final URLRepository urlRepository;
|
||||||
|
private final PredictionService predictionService;
|
||||||
|
@Autowired
|
||||||
|
public URLVerificationService(URLRepository urlRepository, PredictionService predictionService) {
|
||||||
|
this.urlRepository = urlRepository;
|
||||||
|
this.predictionService = predictionService;
|
||||||
|
}
|
||||||
|
|
||||||
public URLVerificationResponse verifyURL(QRCodePayload payload) {
|
// Regular expression pattern for shortening services
|
||||||
URLVerificationResponse response = new URLVerificationResponse();
|
private static final String SHORTENING_PATTERN =
|
||||||
|
"bit\\.ly|goo\\.gl|shorte\\.st|go2l\\.ink|x\\.co|ow\\.ly|t\\.co|tinyurl|tr\\.im|is\\.gd|cli\\.gs|" +
|
||||||
|
"yfrog\\.com|migre\\.me|ff\\.im|tiny\\.cc|url4\\.eu|twit\\.ac|su\\.pr|twurl\\.nl|snipurl\\.com|" +
|
||||||
|
"short\\.to|BudURL\\.com|ping\\.fm|post\\.ly|Just\\.as|bkite\\.com|snipr\\.com|fic\\.kr|loopt\\.us|" +
|
||||||
|
"doiop\\.com|short\\.ie|kl\\.am|wp\\.me|rubyurl\\.com|om\\.ly|to\\.ly|bit\\.do|t\\.co|lnkd\\.in|" +
|
||||||
|
"db\\.tt|qr\\.ae|adf\\.ly|goo\\.gl|bitly\\.com|cur\\.lv|tinyurl\\.com|ow\\.ly|bit\\.ly|ity\\.im|" +
|
||||||
|
"q\\.gs|is\\.gd|po\\.st|bc\\.vc|twitthis\\.com|u\\.to|j\\.mp|buzurl\\.com|cutt\\.us|u\\.bb|yourls\\.org|" +
|
||||||
|
"x\\.co|prettylinkpro\\.com|scrnch\\.me|filoops\\.info|vzturl\\.com|qr\\.net|1url\\.com|tweez\\.me|v\\.gd|" +
|
||||||
|
"tr\\.im|link\\.zip\\.net";
|
||||||
|
|
||||||
|
// Regular expression pattern to match various IP address formats
|
||||||
|
private static final String IP_PATTERN =
|
||||||
|
"(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
|
||||||
|
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\/)|" +
|
||||||
|
"(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
|
||||||
|
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\/)|" +
|
||||||
|
"((0x[0-9a-fA-F]{1,2})\\.(0x[0-9a-fA-F]{1,2})\\.(0x[0-9a-fA-F]{1,2})\\.(0x[0-9a-fA-F]{1,2})\\/)" +
|
||||||
|
"(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|" +
|
||||||
|
"([0-9]+(?:\\.[0-9]+){3}:[0-9]+)|" +
|
||||||
|
"((?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?)";
|
||||||
|
|
||||||
|
// Define a Set of suspicious file extensions
|
||||||
|
private static final Set<String> SUSPICIOUS_EXTENSIONS = Stream.of(
|
||||||
|
".exe", ".bat", ".sh", ".cmd", ".scr", ".pif", ".application", ".gadget",
|
||||||
|
".vb", ".vbs", ".js", ".jse", ".ws", ".wsf", ".msc", ".cpl",
|
||||||
|
".msi", ".ps1", ".py", ".pyc", ".pyo", ".rb", ".bin", ".run", "apk"
|
||||||
|
).collect(Collectors.toUnmodifiableSet());
|
||||||
|
|
||||||
|
|
||||||
|
// Checks if the URL has executable file
|
||||||
|
public String hasExecutableFile(String urlPath) {
|
||||||
|
return Stream.of(urlPath)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.map(path -> {
|
||||||
|
int lastDotIndex = path.lastIndexOf('.');
|
||||||
|
if (lastDotIndex != -1) {
|
||||||
|
return path.substring(lastDotIndex);
|
||||||
|
}
|
||||||
|
return path.contains(".") || path.endsWith("/") ? null : "";
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(extension -> SUSPICIOUS_EXTENSIONS.contains(extension) || extension.isEmpty() ? "Yes" : "")
|
||||||
|
.findFirst()
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
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).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertDB(URLEntity urlEntity) {
|
||||||
|
urlRepository.save(urlEntity);
|
||||||
|
}
|
||||||
|
// Function to breakdown URL into subdomain, domain, topLevelDomain, query params, fragment
|
||||||
|
public URLEntity breakdownURL(String urlString) {
|
||||||
|
URLEntity urlObj = new URLEntity();
|
||||||
try {
|
try {
|
||||||
java.net.URL url = new java.net.URL(payload.getData());
|
//URL url = new URI(encodeUrl(urlString)).toURL();
|
||||||
String protocol = url.getProtocol();
|
URL url = new URI(urlString.replace(" ", "")).toURL();
|
||||||
if ("https".equalsIgnoreCase(protocol)) {
|
// Check for URL encoding in path and query
|
||||||
response.setSecure(true);
|
String query = parseQueryParams(url.getQuery());
|
||||||
response.setMessage("The connection is secure.");
|
String pathEncoding = checkURLEncoding(url.getPath());
|
||||||
} else {
|
String queryEncoding = query != null ? checkURLEncoding(query) : "";
|
||||||
response.setSecure(false);
|
|
||||||
response.setMessage("The connection is not secure.");
|
// Combine encoding results
|
||||||
}
|
urlObj.setUrlEncoding(pathEncoding.equals("Yes") || queryEncoding.equals("Yes") ? "Yes" : "");
|
||||||
|
|
||||||
|
// encode url before proceeding the rest of the checks
|
||||||
|
url = new URI(encodeUrl(urlString)).toURL();
|
||||||
|
String host = url.getHost();
|
||||||
|
populateHostDetails(host, urlObj);
|
||||||
|
|
||||||
|
// Check for deceptive URL
|
||||||
|
urlObj.setHostnameEmbedding(checkDeceptiveUrl(url));
|
||||||
|
|
||||||
|
// Check for Javascript code in url
|
||||||
|
urlObj.setJavascriptCheck(checkForJavascriptCode(urlString));
|
||||||
|
|
||||||
|
// Check for url shortener
|
||||||
|
urlObj.setShorteningService(hasShorteningService(urlString));
|
||||||
|
|
||||||
|
// Check for IP address
|
||||||
|
urlObj.setHasIpAddress(hasIPAddress(urlString));
|
||||||
|
|
||||||
|
// Check for suspicious file extensions
|
||||||
|
urlObj.setHasExecutable(hasExecutableFile(urlString));
|
||||||
|
|
||||||
|
urlObj.setPath(Optional.ofNullable(url.getPath()).filter(p -> !p.isEmpty()).orElse(""));
|
||||||
|
|
||||||
|
urlObj.setQuery(parseQueryParams(url.getQuery()));
|
||||||
|
urlObj.setFragment(Optional.ofNullable(url.getRef()).orElse(""));
|
||||||
|
|
||||||
|
// Check for tracking parameters
|
||||||
|
urlObj.setTrackingDescriptions(getTrackingDescriptions(url.getQuery()));
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
response.setSecure(false);
|
logger.error("Error in breaking down URL: {}", e.getMessage());
|
||||||
response.setMessage("Invalid URL.");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return response;
|
return urlObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateHostDetails(String host, URLEntity urlObj) {
|
||||||
|
logger.info("Host: {}", host);
|
||||||
|
|
||||||
|
if (host != null && !host.isEmpty()) {
|
||||||
|
if (isIpAddress(host)) {
|
||||||
|
// Handle IP address
|
||||||
|
urlObj.setDomain(host);
|
||||||
|
urlObj.setTopLevelDomain(""); // No TLD for IP addresses
|
||||||
|
urlObj.setSubdomain(""); // No subdomain for IP addresses
|
||||||
|
} else {
|
||||||
|
// Handle regular domain name
|
||||||
|
String[] hostParts = host.split("\\.");
|
||||||
|
|
||||||
|
int length = hostParts.length;
|
||||||
|
|
||||||
|
if (length >= 2) {
|
||||||
|
urlObj.setTopLevelDomain(hostParts[length - 1]); // TLD, e.g., "com"
|
||||||
|
urlObj.setDomain(hostParts[length - 2]); // Domain, e.g., "example"
|
||||||
|
urlObj.setSubdomain(length > 2 ? String.join(".", Arrays.copyOfRange(hostParts, 0, length - 2)) : "");
|
||||||
|
} else if (length == 1) {
|
||||||
|
// Handle cases like 'localhost' where there's no TLD
|
||||||
|
urlObj.setDomain(hostParts[0]);
|
||||||
|
urlObj.setTopLevelDomain(""); // No TLD
|
||||||
|
urlObj.setSubdomain(""); // No subdomain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// List of common tracking parameters with their descriptions
|
||||||
|
private static final Map<String, String> TRACKING_DESCRIPTIONS = Map.ofEntries(
|
||||||
|
Map.entry("utm_source", "Campaign Source: Identifies which site sent the traffic."),
|
||||||
|
Map.entry("utm_medium", "Campaign Medium: Identifies what type of link was used."),
|
||||||
|
Map.entry("utm_campaign", "Campaign Name: Identifies a specific product promotion or campaign."),
|
||||||
|
Map.entry("utm_term", "Campaign Term: Identifies search terms."),
|
||||||
|
Map.entry("utm_content", "Campaign Content: Differentiates similar content or links within the same ad."),
|
||||||
|
Map.entry("gclid", "Google Click Identifier: Used by Google Ads to track clicks."),
|
||||||
|
Map.entry("fbclid", "Facebook Click Identifier: Used by Facebook to track clicks."),
|
||||||
|
Map.entry("tracking_id", "Tracking ID: General identifier for tracking purposes."),
|
||||||
|
Map.entry("affiliate_id", "Affiliate ID: Identifies traffic from affiliates."),
|
||||||
|
Map.entry("ref", "Referrer: Identifies the referrer site."),
|
||||||
|
Map.entry("referrer", "Referrer: Identifies the referrer site.")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Regex pattern to capture key-value pairs in the query string
|
||||||
|
private static final Pattern PARAM_PATTERN = Pattern.compile(
|
||||||
|
"(?<key>[^=&]+)=(?<value>[^&]+)",
|
||||||
|
Pattern.CASE_INSENSITIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Static method to detect and return tracking parameter descriptions in a URL
|
||||||
|
private List<String> getTrackingDescriptions(String query) {
|
||||||
|
if (query == null || query.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = PARAM_PATTERN.matcher(query);
|
||||||
|
List<String> foundDescriptions = new ArrayList<>();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String key = matcher.group("key").toLowerCase();
|
||||||
|
String value = URLDecoder.decode(matcher.group("value"), StandardCharsets.UTF_8);
|
||||||
|
if (TRACKING_DESCRIPTIONS.containsKey(key)) {
|
||||||
|
foundDescriptions.add(TRACKING_DESCRIPTIONS.get(key) + ": " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkDeceptiveUrl(URL url) {
|
||||||
|
String[] parts = url.getHost().split("\\.");
|
||||||
|
if (parts.length < 3) return 0;
|
||||||
|
|
||||||
|
Set<String> commonTlds = new HashSet<>(Arrays.asList("com", "org", "net", "edu", "gov"));
|
||||||
|
|
||||||
|
for (int i = parts.length - 2; i >= 1; i--) {
|
||||||
|
if (commonTlds.contains(parts[i]) && !commonTlds.contains(parts[i - 1]) && i != parts.length - 2) {
|
||||||
|
logger.warn("Potentially deceptive URL detected: {} (Suspicious domain: {}.{})",
|
||||||
|
url, parts[i - 1], parts[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkForJavascriptCode(String url) {
|
||||||
|
// Decode the URL
|
||||||
|
String decodedUrl = URLDecoder.decode(url, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// Patterns to detect 'javascript:', '<script>', and 'on*=' attributes
|
||||||
|
List<Pattern> maliciousPatterns = Arrays.asList(
|
||||||
|
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
|
||||||
|
Pattern.compile("<\\s*script", Pattern.CASE_INSENSITIVE),
|
||||||
|
Pattern.compile("on(click|mouseover|load|error|unload|submit|reset|focus|blur|change|select|keydown|keyup|keypress|mousedown|mousemove|mouseup|mouseenter|mouseleave|contextmenu|dblclick)\\s*=", Pattern.CASE_INSENSITIVE)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for any malicious pattern in the URL
|
||||||
|
for (Pattern pattern : maliciousPatterns) {
|
||||||
|
Matcher matcher = pattern.matcher(decodedUrl);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return "Javascript found in URL.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to detect if the URL uses a shortening service
|
||||||
|
private String hasShorteningService(String url) {
|
||||||
|
Pattern pattern = Pattern.compile(SHORTENING_PATTERN, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = pattern.matcher(url);
|
||||||
|
return matcher.find() ? "Yes" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check text encoding in a URL
|
||||||
|
private static String checkURLEncoding(String pathTextPart) {
|
||||||
|
// Decode the text
|
||||||
|
String decodedText = URLDecoder.decode(pathTextPart, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// Check if the decoded text matches the original text
|
||||||
|
return decodedText.equals(pathTextPart) ? "" : "Yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to detect if the URL has an IP address
|
||||||
|
private static String hasIPAddress(String url) {
|
||||||
|
Pattern pattern = Pattern.compile(IP_PATTERN, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = pattern.matcher(url);
|
||||||
|
return matcher.find() ? "URL contains IP address." : "";
|
||||||
|
}
|
||||||
|
// Check if the host is an IP address
|
||||||
|
private boolean isIpAddress(String host) {
|
||||||
|
// Regex to match IPv4 addresses
|
||||||
|
String ipv4Pattern = "\\d+\\.\\d+\\.\\d+\\.\\d+";
|
||||||
|
// Regex to match IPv6 addresses
|
||||||
|
String ipv6Pattern = "([a-fA-F0-9:]+:+)+[a-fA-F0-9]+";
|
||||||
|
|
||||||
|
return host.matches(ipv4Pattern) || host.matches(ipv6Pattern);
|
||||||
|
}
|
||||||
|
private String parseQueryParams(String query) {
|
||||||
|
if (query == null || query.isEmpty()) return "{}";
|
||||||
|
Map<String, String> queryParams = new HashMap<>();
|
||||||
|
for (String param : query.split("&")) {
|
||||||
|
String[] pair = param.split("=", 2);
|
||||||
|
String key = pair[0];
|
||||||
|
String value = pair.length > 1 ? pair[1] : "";
|
||||||
|
if (!key.isEmpty()) {
|
||||||
|
queryParams.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encodeUrl(String urlString) throws MalformedURLException {
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
String protocol = url.getProtocol();
|
||||||
|
String host = url.getHost();
|
||||||
|
int port = url.getPort();
|
||||||
|
String path = url.getPath();
|
||||||
|
String query = url.getQuery();
|
||||||
|
String ref = url.getRef();
|
||||||
|
|
||||||
|
StringBuilder encodedUrl = new StringBuilder();
|
||||||
|
encodedUrl.append(protocol).append("://").append(host);
|
||||||
|
if (port != -1) {
|
||||||
|
encodedUrl.append(":").append(port);
|
||||||
|
}
|
||||||
|
encodedUrl.append(URLEncoder.encode(path, StandardCharsets.UTF_8).replace("%2F", "/"));
|
||||||
|
|
||||||
|
if (query != null) {
|
||||||
|
encodedUrl.append("?").append(URLEncoder.encode(query, StandardCharsets.UTF_8).replace("%3D", "=").replace("%26", "&"));
|
||||||
|
}
|
||||||
|
if (ref != null) {
|
||||||
|
encodedUrl.append("#").append(URLEncoder.encode(ref, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodedUrl.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MalformedURLException("Failed to encode URL: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void countAndTrackRedirects(String urlString, URLEntity details) throws IOException {
|
||||||
|
try {
|
||||||
|
URI uri = new URI(encodeUrl(urlString));
|
||||||
|
URL url = uri.toURL();
|
||||||
|
List<String> redirectChain = new ArrayList<>();
|
||||||
|
List<String> hstsHeaderList = new ArrayList<>();
|
||||||
|
List<Boolean> sslStrippingList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Add the initial URL to the chain
|
||||||
|
redirectChain.add(urlString);
|
||||||
|
boolean redirected;
|
||||||
|
int redirectCount = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
URLConnection testConnection = url.openConnection();
|
||||||
|
|
||||||
|
if (!(testConnection instanceof HttpURLConnection)) {
|
||||||
|
// Handle non-HTTP connections (like mailto:)
|
||||||
|
logger.info("Non-HTTP URL encountered: {}", url);
|
||||||
|
hstsHeaderList.add(INFO_HSTS_NOT_APPLICABLE);
|
||||||
|
sslStrippingList.add(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setInstanceFollowRedirects(false);
|
||||||
|
connection.setConnectTimeout(CONNECTION_TIMEOUT_MS);
|
||||||
|
connection.setReadTimeout(READ_TIMEOUT_MS);
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
redirected = (responseCode >= 300 && responseCode < 400);
|
||||||
|
|
||||||
|
// Checks for HSTS Header
|
||||||
|
hstsHeaderList.add(detectHSTSHeader(url, connection));
|
||||||
|
|
||||||
|
// Handle redirects
|
||||||
|
if (redirected) {
|
||||||
|
// Location header contains the URL to redirect to
|
||||||
|
String newUrl = connection.getHeaderField("Location");
|
||||||
|
if (newUrl == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
URI newUri = uri.resolve(newUrl);
|
||||||
|
// check for SSL stripping during redirect
|
||||||
|
sslStrippingList.add(checkRedirectForSSLStripping(uri, newUri));
|
||||||
|
|
||||||
|
// Handle relative URLs
|
||||||
|
uri = uri.resolve(newUrl);
|
||||||
|
url = uri.toURL();
|
||||||
|
redirectChain.add(url.toString());
|
||||||
|
redirectCount++;
|
||||||
|
logger.info("Redirect #{}: {}",redirectCount, newUrl);
|
||||||
|
} else {
|
||||||
|
// No redirect, so no SSL stripping
|
||||||
|
sslStrippingList.add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect();
|
||||||
|
} while (redirected && redirectCount < MAX_REDIRECT_COUNT);
|
||||||
|
|
||||||
|
details.setRedirect(redirectChain.size() - 1);
|
||||||
|
details.setRedirectChain(redirectChain);
|
||||||
|
details.setSslStripping(sslStrippingList);
|
||||||
|
details.setHstsHeader(hstsHeaderList);
|
||||||
|
} catch (URISyntaxException e){
|
||||||
|
logger.error("Error in breaking down URL: {}", e.getMessage());
|
||||||
|
} catch (SSLHandshakeException e) {
|
||||||
|
logger.error("SSL Handshake Exception: {}", e.getMessage());
|
||||||
|
details.setSslError("SSL Handshake Exception: " + e.getMessage());
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
logger.error("Connection timed out: {}", e.getMessage());
|
||||||
|
details.setDnsError("Connection timed out: " + e.getMessage());
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.error("Unknown Host Exception: {}", e.getMessage());
|
||||||
|
details.setDnsError("Unknown Host Exception: " + e.getMessage());
|
||||||
|
} catch (NoRouteToHostException e) {
|
||||||
|
details.setDnsError("Error: " + e.getMessage());
|
||||||
|
} catch (ConnectException e) {
|
||||||
|
details.setDnsError("Connection Error: " + e.getMessage());
|
||||||
|
} catch (SocketException e) {
|
||||||
|
details.setDnsError("Socket Error: " + e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
details.setDnsError("Exception: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Function to check if the redirect is from HTTPS to HTTP
|
||||||
|
private boolean checkRedirectForSSLStripping(URI originalUri, URI newUri) {
|
||||||
|
return originalUri.getScheme().equalsIgnoreCase("https") &&
|
||||||
|
newUri.getScheme().equalsIgnoreCase("http");
|
||||||
|
}
|
||||||
|
// Function to check if HSTS header is present for HTTPS connections
|
||||||
|
private String detectHSTSHeader(URL url, HttpURLConnection connection) {
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
String hstsHeader = connection.getHeaderField("Strict-Transport-Security");
|
||||||
|
if (hstsHeader != null && !hstsHeader.isEmpty()) {
|
||||||
|
logger.info("HSTS Header detected for {}: {}", url, hstsHeader);
|
||||||
|
return INFO_HSTS_HEADER_PREFIX + hstsHeader;
|
||||||
|
} else {
|
||||||
|
logger.warn("No HSTS Header for HTTPS connection to {}", url);
|
||||||
|
return INFO_NO_HSTS_HEADER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INFO_NON_SECURE_CONNECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Classification using ML Model
|
||||||
|
public String getClassification(URLModel urlModel){
|
||||||
|
String content = urlModel.getData().getContents();
|
||||||
|
// if in whitelist, return Benign and Safe
|
||||||
|
for (String domain : WHITELIST_DOMAINS) {
|
||||||
|
if (content.contains(domain)) {
|
||||||
|
// If in whitelist, set category to BENIGN and return SAFE
|
||||||
|
urlModel.getDetails().setClassifications(CAT_BENIGN);
|
||||||
|
return CLASSIFY_SAFE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call ML model
|
||||||
|
String category = predictionService.predict(urlModel);
|
||||||
|
|
||||||
|
//update in category in url table
|
||||||
|
urlModel.getDetails().setClassifications(category);
|
||||||
|
|
||||||
|
// return classification results
|
||||||
|
if (category.equals(CAT_BENIGN)) {
|
||||||
|
if (!urlModel.getDetails().getTrackingDescriptions().isEmpty() || // contains tracking
|
||||||
|
urlModel.getData().getInfo().getPrefix().equalsIgnoreCase("http://") || // uses http
|
||||||
|
urlModel.getDetails().getSslStripping().contains(true) || // has SSL stripping
|
||||||
|
urlModel.getDetails().getHasExecutable().equalsIgnoreCase("yes") || // contains executable
|
||||||
|
!urlModel.getDetails().getJavascriptCheck().isEmpty() || // contains javascript
|
||||||
|
!urlModel.getDetails().getHasIpAddress().isEmpty() || // contains IP address
|
||||||
|
urlModel.getDetails().getHostnameEmbedding() != null // contains hostname embedding
|
||||||
|
|
||||||
|
) {
|
||||||
|
return CLASSIFY_WARNING;
|
||||||
|
}
|
||||||
|
return CLASSIFY_SAFE;
|
||||||
|
}
|
||||||
|
return CLASSIFY_UNSAFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static array for whitelist domains
|
||||||
|
private static final List<String> WHITELIST_DOMAINS = Arrays.asList(
|
||||||
|
"safeqr.github.io/marketing",
|
||||||
|
"uow.edu.au",
|
||||||
|
"nus.edu.sg",
|
||||||
|
"sim.edu.sg",
|
||||||
|
"sp.edu.sg"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
package com.safeqr.app.qrcode.service;
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.safeqr.app.qrcode.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
import com.safeqr.app.qrcode.entity.WifiEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.WifiRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class WifiVerificationService {
|
||||||
|
private final WifiRepository wifiRepository;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(WifiVerificationService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WifiVerificationService(WifiRepository wifiRepository) {
|
||||||
|
this.wifiRepository = wifiRepository;
|
||||||
|
}
|
||||||
|
public WifiEntity getWifiEntityByQRCodeId(UUID qrCodeId) {
|
||||||
|
logger.info("qrCodeId retrieving: {}", qrCodeId);
|
||||||
|
return wifiRepository.findByQrCodeId(qrCodeId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("Wifi not found for QR Code id: " + qrCodeId));
|
||||||
|
}
|
||||||
|
public void insertDB(WifiEntity wifiEntity) {
|
||||||
|
wifiRepository.save(wifiEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parseWifiString(WifiEntity wifiEntity, String wifiString) {
|
||||||
|
wifiString = wifiString.substring(5);
|
||||||
|
// Split the string by semicolons
|
||||||
|
String[] parts = wifiString.split(";");
|
||||||
|
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part.startsWith("T:")) {
|
||||||
|
wifiEntity.setEncryption(part.substring(2));
|
||||||
|
} else if (part.startsWith("S:")) {
|
||||||
|
wifiEntity.setSsid(part.substring(2));
|
||||||
|
} else if (part.startsWith("P:")) {
|
||||||
|
wifiEntity.setPassword(part.substring(2));
|
||||||
|
} else if (part.startsWith("H:")) {
|
||||||
|
wifiEntity.setHidden(Boolean.parseBoolean(part.substring(2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unescape special characters in SSID and password
|
||||||
|
wifiEntity.setSsid(unescapeString(wifiEntity.getSsid()));
|
||||||
|
wifiEntity.setPassword(unescapeString(wifiEntity.getPassword()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String unescapeString(String input) {
|
||||||
|
return input.replace("\\:", ":")
|
||||||
|
.replace("\\;", ";")
|
||||||
|
.replace("\\,", ",")
|
||||||
|
.replace("\\\\", "\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassification(String encryptionType) {
|
||||||
|
// Check if encryptionType is null
|
||||||
|
if (encryptionType == null) {
|
||||||
|
return CLASSIFY_UNSAFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptionType.equalsIgnoreCase("WPA") ||
|
||||||
|
encryptionType.equalsIgnoreCase("WPA2") ||
|
||||||
|
encryptionType.equalsIgnoreCase("WPA3")) {
|
||||||
|
return CLASSIFY_SAFE;
|
||||||
|
} else if (encryptionType.equalsIgnoreCase("WEP")) {
|
||||||
|
return CLASSIFY_WARNING;
|
||||||
|
} else if (encryptionType.equalsIgnoreCase("nopass")) {
|
||||||
|
return CLASSIFY_UNSAFE;
|
||||||
|
} else {
|
||||||
|
return CLASSIFY_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.safeqr.app.qrcodetips.controller;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||||
|
import com.safeqr.app.qrcodetips.service.QrCodeTipsService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.APIConstants.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(API_VERSION)
|
||||||
|
public class QRCodeTipsController {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(QRCodeTipsController.class);
|
||||||
|
QrCodeTipsService qrCodeTipsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public QRCodeTipsController (QrCodeTipsService qrCodeTipsService) { this.qrCodeTipsService = qrCodeTipsService;}
|
||||||
|
|
||||||
|
@GetMapping(value = API_URL_TIPS_GET)
|
||||||
|
public ResponseEntity<QrCodeTipEntity> getRandomTips() {
|
||||||
|
logger.info("Invoking GET QR Code tips endpoint");
|
||||||
|
return ResponseEntity.ok(qrCodeTipsService.getTips());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.safeqr.app.qrcodetips.entity;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "qr_code_tips", schema = "safeqr")
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QrCodeTipEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@JsonIgnore
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String tips;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.safeqr.app.qrcodetips.repository;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface QrCodeTipRepository extends JpaRepository<QrCodeTipEntity, Long> {
|
||||||
|
|
||||||
|
@Query(value = "SELECT * FROM safeqr.qr_code_tips ORDER BY RANDOM() LIMIT 1", nativeQuery = true)
|
||||||
|
QrCodeTipEntity findRandomTip();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.safeqr.app.qrcodetips.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||||
|
import com.safeqr.app.qrcodetips.repository.QrCodeTipRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class QrCodeTipsService {
|
||||||
|
QrCodeTipRepository qrCodeTipRepository;
|
||||||
|
public QrCodeTipsService (QrCodeTipRepository qrCodeTipRepository) { this.qrCodeTipRepository = qrCodeTipRepository; }
|
||||||
|
public QrCodeTipEntity getTips() {
|
||||||
|
return qrCodeTipRepository.findRandomTip();
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/java/com/safeqr/app/user/.DS_Store
vendored
BIN
src/main/java/com/safeqr/app/user/.DS_Store
vendored
Binary file not shown.
@@ -1,25 +1,85 @@
|
|||||||
package com.safeqr.app.user.controller;
|
package com.safeqr.app.user.controller;
|
||||||
|
|
||||||
|
import static com.safeqr.app.constants.APIConstants.*;
|
||||||
|
import static com.safeqr.app.constants.CommonConstants.HEADER_USER_ID;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import com.safeqr.app.user.dto.BaseResponse;
|
||||||
|
import com.safeqr.app.user.dto.BookmarkRequestDto;
|
||||||
|
import com.safeqr.app.user.dto.ScannedHistoriesDto;
|
||||||
|
import com.safeqr.app.user.dto.UserResponseDto;
|
||||||
import com.safeqr.app.user.service.UserService;
|
import com.safeqr.app.user.service.UserService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/v1")
|
@RequestMapping(API_VERSION)
|
||||||
public class UserController {
|
public class UserController {
|
||||||
@Autowired
|
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
|
||||||
UserService userService;
|
UserService userService;
|
||||||
|
@Autowired
|
||||||
|
public UserController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
|
@GetMapping(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public ResponseEntity<Map<String, String>> version() {
|
public ResponseEntity<Map<String, String>> version() {
|
||||||
System.out.println(userService.getUserByEmail());
|
logger.info("Health Check");
|
||||||
System.out.println("Health Check");
|
|
||||||
return ResponseEntity.ok(Map.of("version","SafeQR v1.0.2"));
|
return ResponseEntity.ok(Map.of("version","SafeQR v1.0.2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = API_URL_USER_GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<UserResponseDto> getUser(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking GET User endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.getUserById(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = API_URL_USER_GET_SCANNED_HISTORIES, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<List<ScannedHistoriesDto>> getUserScannedHistories(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking GET User Scanned Histories endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.getUserScannedHistories(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_USER_DELETE_SCANNED_HISTORIES, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteScannedHistory(@RequestHeader(name = HEADER_USER_ID) String userId, @RequestBody BookmarkRequestDto bookmarkRequestDto) {
|
||||||
|
logger.info("User Id Invoking PUT Delete Single Scanned History endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.deleteScannedHistory(userId, bookmarkRequestDto.getQrCodeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_USER_DELETE_ALL_SCANNED_HISTORIES, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteAllScannedHistories(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking PUT Delete All Scanned Histories endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.deleteAllScannedHistoriesByUserId(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = API_URL_USER_GET_BOOKMARKS, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<List<ScannedHistoriesDto>> getUserBookmarks(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking GET User bookmarks endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.getUserBookmarks(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = API_URL_USER_SET_BOOKMARK, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> setBookmark(@RequestHeader(name = HEADER_USER_ID) String userId, @RequestBody BookmarkRequestDto bookmarkRequestDto) {
|
||||||
|
logger.info("User Id Invoking POST User bookmark endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.setBookmark(userId, bookmarkRequestDto.getQrCodeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_USER_DELETE_BOOKMARK, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteBookmark(@RequestHeader(name = HEADER_USER_ID) String userId, @RequestBody BookmarkRequestDto bookmarkRequestDto) {
|
||||||
|
logger.info("User Id Invoking PUT Delete Single Bookmark endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.deleteBookmark(userId, bookmarkRequestDto.getQrCodeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = API_URL_USER_DELETE_ALL_BOOKMARK, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<BaseResponse> deleteAllBookmark(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||||
|
logger.info("User Id Invoking PUT Delete All Bookmark endpoint: {}", userId);
|
||||||
|
return ResponseEntity.ok(userService.deleteAllBookmarkByUserId(userId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main/java/com/safeqr/app/user/dto/BaseResponse.java
Normal file
11
src/main/java/com/safeqr/app/user/dto/BaseResponse.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.safeqr.app.user.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class BaseResponse {
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.safeqr.app.user.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BookmarkRequestDto {
|
||||||
|
private UUID qrCodeId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.safeqr.app.user.dto;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeEntity;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ScannedHistoriesDto {
|
||||||
|
|
||||||
|
private QRCodeEntity data;
|
||||||
|
private boolean bookmarked;
|
||||||
|
|
||||||
|
public ScannedHistoriesDto(QRCodeEntity qrCodeEntity, boolean bookmarked) {
|
||||||
|
this.data = qrCodeEntity;
|
||||||
|
this.bookmarked = bookmarked;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/com/safeqr/app/user/dto/UserResponseDto.java
Normal file
20
src/main/java/com/safeqr/app/user/dto/UserResponseDto.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.safeqr.app.user.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class UserResponseDto {
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private String email;
|
||||||
|
private String source;
|
||||||
|
private OffsetDateTime dateJoined;
|
||||||
|
private OffsetDateTime dateUpdated;
|
||||||
|
private List<String> roles;
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
package com.safeqr.app.user.entity;
|
package com.safeqr.app.user.entity;
|
||||||
|
|
||||||
|
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
import java.util.List;
|
import java.math.BigInteger;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Entity
|
@Entity
|
||||||
@Data
|
@Data
|
||||||
@Table(name="user", schema = "safeqr")
|
@Table(name="user", schema = "safeqr")
|
||||||
@@ -21,8 +23,21 @@ public class UserEntity {
|
|||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
private String email;
|
private String email;
|
||||||
private OffsetDateTime date_created;
|
|
||||||
private OffsetDateTime date_updated;
|
@Column(name = "date_created")
|
||||||
private String source;
|
private OffsetDateTime dateCreated;
|
||||||
|
|
||||||
|
@Column(name = "date_updated")
|
||||||
|
private OffsetDateTime dateUpdated;
|
||||||
|
|
||||||
|
@Type(ListArrayType.class)
|
||||||
|
@Column(name = "roles", columnDefinition = "text[]")
|
||||||
|
private List<String> roles;
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "source")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Column(name = "gmail_history_id")
|
||||||
|
private BigInteger gmailHistoryId = BigInteger.ZERO;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.safeqr.app.user.model;
|
||||||
|
|
||||||
|
public class CognitoTokenModel {
|
||||||
|
}
|
||||||
@@ -6,5 +6,4 @@ import org.springframework.stereotype.Repository;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<UserEntity, String> {
|
public interface UserRepository extends JpaRepository<UserEntity, String> {
|
||||||
UserEntity findByEmail(String email);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,127 @@
|
|||||||
package com.safeqr.app.user.service;
|
package com.safeqr.app.user.service;
|
||||||
|
|
||||||
|
import com.safeqr.app.exceptions.ResourceAlreadyExists;
|
||||||
|
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.ScanHistoryEntity;
|
||||||
|
import com.safeqr.app.qrcode.repository.ScanBookmarkRepository;
|
||||||
|
import com.safeqr.app.qrcode.repository.ScanHistoryRepository;
|
||||||
|
import com.safeqr.app.user.dto.BaseResponse;
|
||||||
|
import com.safeqr.app.user.dto.ScannedHistoriesDto;
|
||||||
|
import com.safeqr.app.user.dto.UserResponseDto;
|
||||||
import com.safeqr.app.user.entity.UserEntity;
|
import com.safeqr.app.user.entity.UserEntity;
|
||||||
import com.safeqr.app.user.repository.UserRepository;
|
import com.safeqr.app.user.repository.UserRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService {
|
public class UserService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
public UserService(UserRepository userRepository,
|
||||||
|
ScanHistoryRepository scanHistoryRepository,
|
||||||
|
ScanBookmarkRepository scanBookmarkRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.scanHistoryRepository = scanHistoryRepository;
|
||||||
|
this.scanBookmarkRepository = scanBookmarkRepository;
|
||||||
|
}
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final ScanHistoryRepository scanHistoryRepository;
|
||||||
|
private final ScanBookmarkRepository scanBookmarkRepository;
|
||||||
|
|
||||||
public String getUserByEmail() {
|
public UserResponseDto getUserById(String userId) {
|
||||||
|
// Find user by id
|
||||||
|
UserEntity userEntity = userRepository.findById(userId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundExceptions("User id not found: " + userId));
|
||||||
|
|
||||||
// Retrieve the user by email
|
// Map to DTO before returning to controller
|
||||||
UserEntity retrievedUser = userRepository.findByEmail("piggyinu@gmail.com");
|
return UserResponseDto.builder()
|
||||||
if (retrievedUser != null) {
|
.id(userEntity.getId())
|
||||||
return "User found: " + retrievedUser.getName();
|
.email(userEntity.getEmail())
|
||||||
|
.name(userEntity.getName())
|
||||||
|
.source(userEntity.getSource())
|
||||||
|
.dateJoined(userEntity.getDateCreated())
|
||||||
|
.dateUpdated(userEntity.getDateUpdated())
|
||||||
|
.roles(userEntity.getRoles())
|
||||||
|
.status(userEntity.getStatus())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
return "User not found";
|
public UserEntity getUserByIdForGmail(String userId){
|
||||||
|
return userRepository.findById(userId)
|
||||||
|
.orElseThrow(()-> new ResourceNotFoundExceptions("User id not found: " + userId));
|
||||||
|
}
|
||||||
|
public UserEntity updateUserEntity(UserEntity userEntity) {
|
||||||
|
return userRepository.save(userEntity);
|
||||||
|
}
|
||||||
|
public List<ScannedHistoriesDto> getUserScannedHistories(String userId) {
|
||||||
|
return scanHistoryRepository.findAllQRCodesByUserId(userId);
|
||||||
|
}
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteScannedHistory(String userId, UUID qrCodeId) {
|
||||||
|
int updatedCount = scanHistoryRepository.updateScannedHistoryToInactive(userId, qrCodeId);
|
||||||
|
// throw exception if bookmark not found
|
||||||
|
if (updatedCount < 1)
|
||||||
|
throw new ResourceNotFoundExceptions("Scanned QR Code not found");
|
||||||
|
|
||||||
|
return BaseResponse.builder().message("Scanned QR Code deleted successfully").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteAllScannedHistoriesByUserId(String userId) {
|
||||||
|
int updatedCount = scanHistoryRepository.updateScannedHistoriesToInactiveByUserId(userId);
|
||||||
|
|
||||||
|
return (updatedCount < 1) ?
|
||||||
|
BaseResponse.builder().message("No QR Code not found").build():
|
||||||
|
BaseResponse.builder().message("All scanned QR Code deleted successfully").build();
|
||||||
|
}
|
||||||
|
public List<ScannedHistoriesDto> getUserBookmarks(String userId) {
|
||||||
|
return scanHistoryRepository.findAllBookmarksByUserId(userId);
|
||||||
|
}
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse setBookmark(String userId, UUID qrCodeId) {
|
||||||
|
// Check if the bookmark already exists
|
||||||
|
Optional<ScanHistoryEntity> existingBookmark = scanHistoryRepository.findByUserIdAndQrCodeId(userId, qrCodeId);
|
||||||
|
|
||||||
|
// throw exception if bookmark already exists
|
||||||
|
if (existingBookmark.isPresent()) {
|
||||||
|
throw new ResourceAlreadyExists("Bookmark already exists!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save bookmark by updating booked to true
|
||||||
|
int updatedCount = scanHistoryRepository.updateBookmarkStatusToActive(userId, qrCodeId);
|
||||||
|
|
||||||
|
if (updatedCount < 1)
|
||||||
|
throw new ResourceNotFoundExceptions("Unable to create bookmark. The QR code may not exist.");
|
||||||
|
|
||||||
|
return BaseResponse.builder().message("Bookmark saved successfully").build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteBookmark(String userId, UUID qrCodeId) {
|
||||||
|
int updatedCount = scanHistoryRepository.updateBookmarkStatusToInactive(userId, qrCodeId);
|
||||||
|
// throw exception if bookmark not found
|
||||||
|
if (updatedCount < 1)
|
||||||
|
throw new ResourceNotFoundExceptions("Bookmark not found");
|
||||||
|
|
||||||
|
return BaseResponse.builder().message("Bookmark deleted successfully").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public BaseResponse deleteAllBookmarkByUserId(String userId) {
|
||||||
|
int updatedCount = scanHistoryRepository.updateBookmarkStatusToInactiveByUserId(userId);
|
||||||
|
|
||||||
|
return (updatedCount < 1) ?
|
||||||
|
BaseResponse.builder().message("No Bookmark not found").build():
|
||||||
|
BaseResponse.builder().message("All Bookmarks deleted successfully").build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/main/java/com/safeqr/app/utils/AsyncConfig.java
Normal file
34
src/main/java/com/safeqr/app/utils/AsyncConfig.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user