includes QRCodeURL entity and redirection logic
This commit is contained in:
@@ -6,4 +6,5 @@ public class CommonConstants {
|
|||||||
}
|
}
|
||||||
public static final String HEADER_USER_ID = "X-USER-ID";
|
public static final String HEADER_USER_ID = "X-USER-ID";
|
||||||
public static final String DEFAULT_QR_CODE_TYPE = "TEXT";
|
public static final String DEFAULT_QR_CODE_TYPE = "TEXT";
|
||||||
|
public static final int MAX_REDIRECT_COUNT = 20;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.safeqr.app.constants.CommonConstants;
|
|||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
||||||
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
||||||
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
||||||
import com.safeqr.app.qrcode.dto.response.ScanResponse;
|
import com.safeqr.app.qrcode.dto.response.BaseScanResponse;
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
import com.safeqr.app.qrcode.entity.QRCodeType;
|
||||||
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.RedirectCountService;
|
||||||
@@ -44,8 +44,8 @@ public class QRCodeTypeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = APIConstants.API_URL_QRCODE_SCAN, produces = MediaType.APPLICATION_JSON_VALUE)
|
@PostMapping(value = APIConstants.API_URL_QRCODE_SCAN, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public ResponseEntity<ScanResponse> scanQRCode(@RequestBody QRCodePayload payload,
|
public ResponseEntity<BaseScanResponse> scanQRCode(@RequestBody QRCodePayload payload,
|
||||||
@RequestHeader(required = false, name = CommonConstants.HEADER_USER_ID) String userId) {
|
@RequestHeader(required = false, name = CommonConstants.HEADER_USER_ID) String userId) {
|
||||||
logger.info("Invoking scan endpoint");
|
logger.info("Invoking scan endpoint");
|
||||||
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ package com.safeqr.app.qrcode.dto.response;
|
|||||||
|
|
||||||
import com.safeqr.app.qrcode.entity.QRCode;
|
import com.safeqr.app.qrcode.entity.QRCode;
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
import com.safeqr.app.qrcode.entity.QRCodeType;
|
||||||
import lombok.Builder;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@SuperBuilder
|
||||||
public class ScanResponse {
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BaseScanResponse {
|
||||||
private QRCode scannedQRCode;
|
private QRCode scannedQRCode;
|
||||||
private QRCodeType qrCodeType;
|
private QRCodeType qrCodeType;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.safeqr.app.qrcode.dto.response;
|
||||||
|
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeURL;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
@SuperBuilder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public final class URLResponse extends BaseScanResponse{
|
||||||
|
private QRCodeURL qrCodeURL;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.safeqr.app.qrcode.entity;
|
package com.safeqr.app.qrcode.entity;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -8,6 +9,8 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -16,7 +19,7 @@ import java.util.UUID;
|
|||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class URL {
|
public class QRCodeURL {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@@ -25,6 +28,7 @@ public class URL {
|
|||||||
@Column(updatable = false, nullable = false)
|
@Column(updatable = false, nullable = false)
|
||||||
private UUID id;
|
private UUID id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
@Column(name = "qr_code_id")
|
@Column(name = "qr_code_id")
|
||||||
private UUID qrCodeId;
|
private UUID qrCodeId;
|
||||||
|
|
||||||
@@ -34,9 +38,15 @@ public class URL {
|
|||||||
|
|
||||||
private String topLevelDomain;
|
private String topLevelDomain;
|
||||||
|
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String query;
|
private String query;
|
||||||
|
|
||||||
private String fragment;
|
private String fragment;
|
||||||
|
|
||||||
private int redirect = 0;
|
private int redirect = 0;
|
||||||
|
|
||||||
|
@Column(name = "redirect_chain")
|
||||||
|
private List<String> redirectChain;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.safeqr.app.qrcode.repository;
|
package com.safeqr.app.qrcode.repository;
|
||||||
|
|
||||||
import com.safeqr.app.qrcode.entity.URL;
|
import com.safeqr.app.qrcode.entity.QRCodeURL;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface URLRepository extends JpaRepository<URL, UUID> {
|
public interface URLRepository extends JpaRepository<QRCodeURL, UUID> {
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,16 @@ package com.safeqr.app.qrcode.service;
|
|||||||
|
|
||||||
import com.safeqr.app.constants.CommonConstants;
|
import com.safeqr.app.constants.CommonConstants;
|
||||||
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
import com.safeqr.app.qrcode.dto.QRCodePayload;
|
||||||
import com.safeqr.app.qrcode.dto.response.ScanResponse;
|
import com.safeqr.app.qrcode.dto.response.BaseScanResponse;
|
||||||
|
import com.safeqr.app.qrcode.dto.response.URLResponse;
|
||||||
import com.safeqr.app.qrcode.entity.QRCode;
|
import com.safeqr.app.qrcode.entity.QRCode;
|
||||||
import com.safeqr.app.qrcode.entity.QRCodeType;
|
import com.safeqr.app.qrcode.entity.QRCodeType;
|
||||||
|
import com.safeqr.app.qrcode.entity.QRCodeURL;
|
||||||
import com.safeqr.app.qrcode.entity.ScanHistory;
|
import com.safeqr.app.qrcode.entity.ScanHistory;
|
||||||
import com.safeqr.app.qrcode.repository.QRCodeRepository;
|
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 com.safeqr.app.qrcode.repository.ScanHistoryRepository;
|
||||||
|
import com.safeqr.app.qrcode.repository.URLRepository;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -17,9 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -35,6 +41,8 @@ public class QRCodeTypeService {
|
|||||||
private ScanHistoryRepository scanHistoryRepository;
|
private ScanHistoryRepository scanHistoryRepository;
|
||||||
@Autowired
|
@Autowired
|
||||||
private QRCodeRepository qrCodeRepository;
|
private QRCodeRepository qrCodeRepository;
|
||||||
|
@Autowired
|
||||||
|
private URLRepository urlRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SafeBrowsingService safeBrowsingService;
|
private SafeBrowsingService safeBrowsingService;
|
||||||
@@ -57,7 +65,7 @@ public class QRCodeTypeService {
|
|||||||
return configs;
|
return configs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScanResponse scanQRCode(String userId, QRCodePayload payload) {
|
public BaseScanResponse scanQRCode(String userId, QRCodePayload payload) {
|
||||||
String data = payload.getData();
|
String data = payload.getData();
|
||||||
logger.info("scanQRCode: userId={}, data={}", userId, data);
|
logger.info("scanQRCode: userId={}, data={}", userId, data);
|
||||||
|
|
||||||
@@ -72,9 +80,6 @@ public class QRCodeTypeService {
|
|||||||
.createdAt(LocalDateTime.now())
|
.createdAt(LocalDateTime.now())
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
// Insert qrcode into respective table based on type
|
|
||||||
insertIntoRespectiveTable(scannedQR);
|
|
||||||
|
|
||||||
// Insert into Scan History table if userId is not null
|
// Insert into Scan History table if userId is not null
|
||||||
logger.info("scanQRCode: scannedQR new ID={}", scannedQR.getId());
|
logger.info("scanQRCode: scannedQR new ID={}", scannedQR.getId());
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
@@ -84,11 +89,8 @@ public class QRCodeTypeService {
|
|||||||
.scanStatus(ScanHistory.ScanStatus.ACTIVE)
|
.scanStatus(ScanHistory.ScanStatus.ACTIVE)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
// Insert into various tables
|
||||||
return ScanResponse.builder()
|
return insertIntoRespectiveTable(scannedQR, qrType);
|
||||||
.scannedQRCode(scannedQR)
|
|
||||||
.qrCodeType(qrType)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
private QRCodeType getQRCodeType(String data) {
|
private QRCodeType getQRCodeType(String data) {
|
||||||
return configs.stream()
|
return configs.stream()
|
||||||
@@ -96,56 +98,108 @@ public class QRCodeTypeService {
|
|||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(defaultQRCodeType);
|
.orElse(defaultQRCodeType);
|
||||||
}
|
}
|
||||||
private void insertIntoRespectiveTable(QRCode qrCode) {
|
private BaseScanResponse insertIntoRespectiveTable(QRCode qrCode, QRCodeType qrCodeType) {
|
||||||
try {
|
try {
|
||||||
String url = qrCode.getContents();
|
String url = qrCode.getContents();
|
||||||
Map<String, Object> breakdown = breakdownURL(url);
|
QRCodeURL urlObj = breakdownURL(url);
|
||||||
breakdown.forEach((key, value) -> logger.info("{}: {}", key, value));
|
urlObj.setQrCodeId(qrCode.getId());
|
||||||
} catch (MalformedURLException e) {
|
List<String> redirectChain = countAndTrackRedirects(url);
|
||||||
e.printStackTrace();
|
logger.info("Redirect chain: {}", redirectChain);
|
||||||
|
for (int i = 0; i < redirectChain.size(); i++) {
|
||||||
|
logger.info((i == 0 ? "Initial URL: " : "Redirect #" + i + ": ") + redirectChain.get(i));
|
||||||
|
}
|
||||||
|
urlObj.setRedirect(redirectChain.size() - 1);
|
||||||
|
urlObj.setRedirectChain(redirectChain);
|
||||||
|
urlRepository.save(urlObj);
|
||||||
|
|
||||||
|
return URLResponse.builder()
|
||||||
|
.scannedQRCode(qrCode)
|
||||||
|
.qrCodeType(qrCodeType)
|
||||||
|
.qrCodeURL(urlObj)
|
||||||
|
.build();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error: ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return BaseScanResponse.builder()
|
||||||
|
.scannedQRCode(qrCode)
|
||||||
|
.qrCodeType(qrCodeType)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
// Function to breakdown URL into subdomain, domain, topLevelDomain, query params, fragment
|
// Function to breakdown URL into subdomain, domain, topLevelDomain, query params, fragment
|
||||||
public Map<String, Object> breakdownURL(String urlString) throws MalformedURLException {
|
public QRCodeURL breakdownURL(String urlString) throws MalformedURLException {
|
||||||
URL url = new URL(urlString);
|
URL url = new URL(urlString);
|
||||||
Map<String, Object> breakdown = new HashMap<>();
|
QRCodeURL urlObj = new QRCodeURL();
|
||||||
|
|
||||||
String host = url.getHost();
|
String host = url.getHost();
|
||||||
String[] hostParts = host.split("\\.");
|
String[] hostParts = host.split("\\.");
|
||||||
|
|
||||||
String subdomain = "";
|
String subdomain = "";
|
||||||
String domain = "";
|
|
||||||
String topLevelDomain = "";
|
|
||||||
|
|
||||||
if (hostParts.length >= 2) {
|
if (hostParts.length >= 2) {
|
||||||
topLevelDomain = hostParts[hostParts.length - 1];
|
urlObj.setTopLevelDomain(hostParts[hostParts.length - 1]);
|
||||||
domain = hostParts[hostParts.length - 2];
|
urlObj.setDomain(hostParts[hostParts.length - 2]);
|
||||||
if (hostParts.length > 2) {
|
if (hostParts.length > 2) {
|
||||||
subdomain = String.join(".", java.util.Arrays.copyOfRange(hostParts, 0, hostParts.length - 2));
|
subdomain = String.join(".", java.util.Arrays.copyOfRange(hostParts, 0, hostParts.length - 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
breakdown.put("Subdomain", subdomain.isEmpty() ? "None" : subdomain);
|
urlObj.setSubdomain(subdomain);
|
||||||
breakdown.put("Domain", domain);
|
|
||||||
breakdown.put("Top Level Domain", topLevelDomain);
|
String path = url.getPath();
|
||||||
|
urlObj.setPath(path.isEmpty() ? "/" : path);
|
||||||
|
|
||||||
String query = url.getQuery();
|
String query = url.getQuery();
|
||||||
|
Map<String, String> queryParams = new HashMap<>();
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
Map<String, String> queryParams = new HashMap<>();
|
|
||||||
for (String param : query.split("&")) {
|
for (String param : query.split("&")) {
|
||||||
String[] pair = param.split("=");
|
String[] pair = param.split("=");
|
||||||
queryParams.put(pair[0], pair.length > 1 ? pair[1] : "");
|
queryParams.put(pair[0], pair.length > 1 ? pair[1] : "");
|
||||||
}
|
}
|
||||||
breakdown.put("Query Parameters", queryParams);
|
logger.info("queryParams: {}", queryParams);
|
||||||
} else {
|
|
||||||
breakdown.put("Query Parameters", "None");
|
|
||||||
}
|
}
|
||||||
|
urlObj.setQuery(queryParams.toString());
|
||||||
|
|
||||||
String fragment = url.getRef();
|
String fragment = url.getRef();
|
||||||
breakdown.put("Fragment", fragment != null ? fragment : "None");
|
|
||||||
|
|
||||||
return breakdown;
|
urlObj.setFragment(fragment);
|
||||||
|
|
||||||
|
return urlObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> countAndTrackRedirects(String urlString) throws IOException {
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
List<String> redirectChain = new ArrayList<>();
|
||||||
|
redirectChain.add(urlString); // Add the initial URL to the chain
|
||||||
|
boolean redirected;
|
||||||
|
int redirectCount = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setInstanceFollowRedirects(false);
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
redirected = (responseCode >= 300 && responseCode < 400);
|
||||||
|
|
||||||
|
if (redirected) {
|
||||||
|
String newUrl = connection.getHeaderField("Location");
|
||||||
|
if (newUrl == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Handle relative URLs
|
||||||
|
if (!newUrl.startsWith("http://") && !newUrl.startsWith("https://")) {
|
||||||
|
newUrl = new URL(url, newUrl).toString();
|
||||||
|
}
|
||||||
|
url = new URL(newUrl);
|
||||||
|
redirectChain.add(newUrl);
|
||||||
|
redirectCount++;
|
||||||
|
logger.info("Redirect #{}: {}",redirectCount, newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect();
|
||||||
|
} while (redirected && redirectCount < CommonConstants.MAX_REDIRECT_COUNT);
|
||||||
|
|
||||||
|
return redirectChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<String> detectType(QRCodePayload payload) {
|
public Mono<String> detectType(QRCodePayload payload) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ spring.datasource.username=${SERVER_DB_USERNAME}
|
|||||||
spring.datasource.password=${SERVER_DB_PASSWORD}
|
spring.datasource.password=${SERVER_DB_PASSWORD}
|
||||||
spring.datasource.driver-class-name=${SERVER_DB_DRIVER_CLASS_NAME}
|
spring.datasource.driver-class-name=${SERVER_DB_DRIVER_CLASS_NAME}
|
||||||
#spring.jpa.database-platform=${SERVER_DB_DIALECT}
|
#spring.jpa.database-platform=${SERVER_DB_DIALECT}
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=none
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
spring.jpa.properties.hibernate.format_sql=true
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user