diff --git a/.gitignore b/.gitignore
index 6b0636e..ba182bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,6 +127,8 @@ buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
+# ignore all json
+*.json
# User-generated content
img_db/
@@ -149,3 +151,11 @@ src/main/resources/image/checkup_media/34/VID_34_20250604_120241.mp4
src/main/resources/image/checkup_media/8/IMG_8_20250604_112523.png
src/main/resources/image/checkup_media/8/IMG_8_20250604_112526.png
src/main/resources/image/checkup_media/8/VID_8_20250604_115008.mp4
+src/main/resources/client_secret_1063629801433-01ubsp30l0sabhbk28q2esh9c3bhjgka.apps.googleusercontent.com.json
+GOOGLE_DRIVE_SETUP.md
+src/main/resources/google-drive-service-account.json
+
+tokens/StoredCredential
+src/main/resources/database/BSK.db
+src/main/resources/database/BSK.db
+src/main/resources/database/BSK.db
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 9eb8ca9..56bde02 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,20 @@
{
"version": "0.2.0",
"configurations": [
+ {
+ "type": "java",
+ "name": "GoogleDriveOAuthTest",
+ "request": "launch",
+ "mainClass": "BsK.server.test.GoogleDriveOAuthTest",
+ "projectName": "ldS_BsK"
+ },
+ {
+ "type": "java",
+ "name": "GoogleDriveTest",
+ "request": "launch",
+ "mainClass": "BsK.server.test.GoogleDriveTest",
+ "projectName": "ldS_BsK"
+ },
{
"type": "java",
"name": "DateUtils",
diff --git a/medical_invoice.pdf b/medical_invoice.pdf
index acaf2f4..d65b208 100644
Binary files a/medical_invoice.pdf and b/medical_invoice.pdf differ
diff --git a/pom.xml b/pom.xml
index 7ed7cdd..bedad98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -273,6 +273,39 @@
jasperreports-pdf
7.0.3
+
+
+
+ com.google.apis
+ google-api-services-drive
+ v3-rev20240914-2.0.0
+
+
+
+
+ com.google.auth
+ google-auth-library-oauth2-http
+ 1.27.0
+
+
+
+
+ com.google.oauth-client
+ google-oauth-client-jetty
+ 1.36.0
+
+
+
+
+ com.google.zxing
+ core
+ 3.5.3
+
+
+ com.google.zxing
+ javase
+ 3.5.3
+
diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
index 25d9b35..4150b1c 100644
--- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
+++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
@@ -1905,7 +1905,11 @@ public class CheckUpPage extends JPanel {
suggestionField.setText(selectedPatient.getSuggestion()); // De nghi
diagnosisField.setText(selectedPatient.getDiagnosis()); // Chuan doan
conclusionField.setText(selectedPatient.getConclusion()); // Ket luan
- recheckupDatePicker.getJFormattedTextField().setText(DateUtils.convertToDisplayFormat(selectedPatient.getReCheckupDate())); // Ngày khám lại
+ if (selectedPatient.getReCheckupDate() != null) {
+ recheckupDatePicker.getJFormattedTextField().setText(DateUtils.convertToDisplayFormat(selectedPatient.getReCheckupDate()));
+ } else {
+ recheckupDatePicker.getJFormattedTextField().setText("");
+ }
// Handle RTF notes content
String notes = selectedPatient.getNotes();
diff --git a/src/main/java/BsK/client/ui/component/QueueViewPage/QueueViewPage.java b/src/main/java/BsK/client/ui/component/QueueViewPage/QueueViewPage.java
index ae6ffd7..ec0b180 100644
--- a/src/main/java/BsK/client/ui/component/QueueViewPage/QueueViewPage.java
+++ b/src/main/java/BsK/client/ui/component/QueueViewPage/QueueViewPage.java
@@ -1,6 +1,7 @@
package BsK.client.ui.component.QueueViewPage;
import BsK.client.LocalStorage;
+import BsK.common.util.date.DateUtils;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@@ -288,26 +289,32 @@ public class QueueViewPage extends JFrame {
SimpleDateFormat inputDateFormat = new SimpleDateFormat("dd/MM/yyyy");
for (String[] row : fullQueueData) {
- if (row.length > 3) {
+ if (row.length > 15) { // Changed from > 3 to > 15 to ensure we have DOB data
String maKhamBenh = row[0];
String ho = row[2];
String ten = row[3];
String hoVaTen = ho + " " + ten;
- String dateStr = row[1];
+ String customerDob = row[15]; // Use customer DOB instead of checkup date
String namSinh = "N/A";
- try {
- Date parsedDate;
- if (dateStr.matches("\\d+")) {
- parsedDate = new Date(Long.parseLong(dateStr));
- } else {
- parsedDate = inputDateFormat.parse(dateStr);
+ if (customerDob != null && !customerDob.isEmpty()) {
+ // Extract year from DOB using DateUtils for consistency
+ int year = DateUtils.extractYearFromTimestamp(customerDob);
+ if (year != -1) {
+ namSinh = String.valueOf(year);
+ } else {
+ // Fallback: try to parse as date string and extract year
+ try {
+ Date dobDate = DateUtils.convertToDate(customerDob);
+ if (dobDate != null) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(dobDate);
+ namSinh = String.valueOf(calendar.get(Calendar.YEAR));
+ }
+ } catch (Exception e) {
+ System.err.println("Lỗi phân tích ngày sinh cho năm trên màn hình chờ TV: " + customerDob + " - " + e.getMessage());
+ }
}
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(parsedDate);
- namSinh = String.valueOf(calendar.get(Calendar.YEAR));
- } catch (ParseException | NumberFormatException e) {
- System.err.println("Lỗi phân tích ngày cho năm trên màn hình chờ TV: " + dateStr + " - " + e.getMessage());
}
tvDataList.add(new Object[]{maKhamBenh, hoVaTen, namSinh});
}
diff --git a/src/main/java/BsK/server/service/GoogleDriveService.java b/src/main/java/BsK/server/service/GoogleDriveService.java
new file mode 100644
index 0000000..ae7aeb2
--- /dev/null
+++ b/src/main/java/BsK/server/service/GoogleDriveService.java
@@ -0,0 +1,243 @@
+package BsK.server.service;
+
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.FileContent;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.gson.GsonFactory;
+import com.google.api.services.drive.Drive;
+import com.google.api.services.drive.DriveScopes;
+import com.google.api.services.drive.model.File;
+import com.google.api.services.drive.model.FileList;
+import com.google.auth.http.HttpCredentialsAdapter;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@Slf4j
+public class GoogleDriveService {
+ private static final String APPLICATION_NAME = "BSK Clinic Management System";
+ private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
+ private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE);
+
+ // Path to service account key file (will be configured)
+ private static final String SERVICE_ACCOUNT_KEY_PATH = "src/main/resources/google-drive-service-account.json";
+
+ private Drive driveService;
+ private String rootFolderId; // BSK Clinic root folder
+
+ public GoogleDriveService() {
+ try {
+ this.driveService = buildDriveService();
+ this.rootFolderId = getOrCreateRootFolder();
+ log.info("Google Drive service initialized successfully");
+ } catch (Exception e) {
+ log.error("Failed to initialize Google Drive service", e);
+ throw new RuntimeException("Google Drive service initialization failed", e);
+ }
+ }
+
+ private Drive buildDriveService() throws GeneralSecurityException, IOException {
+ GoogleCredentials credentials = ServiceAccountCredentials
+ .fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH))
+ .createScoped(SCOPES);
+
+ return new Drive.Builder(
+ GoogleNetHttpTransport.newTrustedTransport(),
+ JSON_FACTORY,
+ new HttpCredentialsAdapter(credentials))
+ .setApplicationName(APPLICATION_NAME)
+ .build();
+ }
+
+ /**
+ * Get or create the root folder for BSK Clinic
+ */
+ private String getOrCreateRootFolder() throws IOException {
+ String folderName = "BSK_Clinic_Patient_Files";
+
+ // Search for existing root folder
+ String query = "name='" + folderName + "' and mimeType='application/vnd.google-apps.folder' and trashed=false";
+ FileList result = driveService.files().list()
+ .setQ(query)
+ .setSpaces("drive")
+ .execute();
+
+ List files = result.getFiles();
+ if (files != null && !files.isEmpty()) {
+ String folderId = files.get(0).getId();
+ log.info("Found existing root folder: {} (ID: {})", folderName, folderId);
+ return folderId;
+ }
+
+ // Create root folder if it doesn't exist
+ File folderMetadata = new File();
+ folderMetadata.setName(folderName);
+ folderMetadata.setMimeType("application/vnd.google-apps.folder");
+
+ File folder = driveService.files().create(folderMetadata)
+ .setFields("id")
+ .execute();
+
+ log.info("Created new root folder: {} (ID: {})", folderName, folder.getId());
+ return folder.getId();
+ }
+
+ /**
+ * Create a folder for a specific patient
+ */
+ public String createPatientFolder(String patientId, String patientName) throws IOException {
+ String folderName = "Patient_" + patientId + "_" + sanitizeFileName(patientName);
+
+ // Check if folder already exists
+ Optional existingFolderId = findPatientFolder(patientId);
+ if (existingFolderId.isPresent()) {
+ log.info("Patient folder already exists: {} (ID: {})", folderName, existingFolderId.get());
+ return existingFolderId.get();
+ }
+
+ // Create new patient folder
+ File folderMetadata = new File();
+ folderMetadata.setName(folderName);
+ folderMetadata.setMimeType("application/vnd.google-apps.folder");
+ folderMetadata.setParents(Collections.singletonList(rootFolderId));
+
+ File folder = driveService.files().create(folderMetadata)
+ .setFields("id")
+ .execute();
+
+ log.info("Created patient folder: {} (ID: {})", folderName, folder.getId());
+ return folder.getId();
+ }
+
+ /**
+ * Find existing patient folder by patient ID
+ */
+ private Optional findPatientFolder(String patientId) throws IOException {
+ String query = "name contains 'Patient_" + patientId + "_' and mimeType='application/vnd.google-apps.folder' and trashed=false and '" + rootFolderId + "' in parents";
+
+ FileList result = driveService.files().list()
+ .setQ(query)
+ .setSpaces("drive")
+ .execute();
+
+ List files = result.getFiles();
+ if (files != null && !files.isEmpty()) {
+ return Optional.of(files.get(0).getId());
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Upload a file to a patient's folder
+ */
+ public String uploadFile(String patientFolderId, java.io.File localFile, String fileName) throws IOException {
+ String mimeType = determineMimeType(fileName);
+
+ File fileMetadata = new File();
+ fileMetadata.setName(fileName);
+ fileMetadata.setParents(Collections.singletonList(patientFolderId));
+
+ FileContent mediaContent = new FileContent(mimeType, localFile);
+
+ File uploadedFile = driveService.files().create(fileMetadata, mediaContent)
+ .setFields("id,webViewLink,webContentLink")
+ .execute();
+
+ log.info("Uploaded file: {} (ID: {})", fileName, uploadedFile.getId());
+ return uploadedFile.getId();
+ }
+
+ /**
+ * Get the public sharing URL for a folder
+ */
+ public String getFolderSharingUrl(String folderId) throws IOException {
+ // Make the folder publicly viewable
+ com.google.api.services.drive.model.Permission permission = new com.google.api.services.drive.model.Permission();
+ permission.setType("anyone");
+ permission.setRole("reader");
+
+ driveService.permissions().create(folderId, permission).execute();
+
+ // Get the folder details with web view link
+ File folder = driveService.files().get(folderId)
+ .setFields("webViewLink")
+ .execute();
+
+ return folder.getWebViewLink();
+ }
+
+ /**
+ * Create a test folder and upload sample files
+ */
+ public void runConnectionTest() throws IOException {
+ log.info("Starting Google Drive connection test...");
+
+ // Create test folder
+ String testFolderId = createPatientFolder("TEST001", "Test Patient");
+
+ // Create a test text file
+ java.io.File testFile = new java.io.File("test-connection.txt");
+ try {
+ java.io.FileWriter writer = new java.io.FileWriter(testFile);
+ writer.write("BSK Clinic - Google Drive Connection Test\n");
+ writer.write("Timestamp: " + java.time.LocalDateTime.now() + "\n");
+ writer.write("This file verifies that the Google Drive integration is working correctly.\n");
+ writer.close();
+
+ // Upload the test file
+ String fileId = uploadFile(testFolderId, testFile, "connection-test.txt");
+
+ // Get sharing URL
+ String folderUrl = getFolderSharingUrl(testFolderId);
+
+ log.info("✅ Google Drive connection test successful!");
+ log.info("📁 Test folder URL: {}", folderUrl);
+ log.info("📄 Test file ID: {}", fileId);
+
+ // Clean up test file
+ testFile.delete();
+
+ } catch (Exception e) {
+ log.error("❌ Google Drive connection test failed", e);
+ if (testFile.exists()) {
+ testFile.delete();
+ }
+ throw e;
+ }
+ }
+
+ private String sanitizeFileName(String fileName) {
+ // Remove or replace invalid characters for file names
+ return fileName.replaceAll("[^a-zA-Z0-9._-]", "_");
+ }
+
+ private String determineMimeType(String fileName) {
+ String lowerFileName = fileName.toLowerCase();
+ if (lowerFileName.endsWith(".pdf")) {
+ return "application/pdf";
+ } else if (lowerFileName.endsWith(".jpg") || lowerFileName.endsWith(".jpeg")) {
+ return "image/jpeg";
+ } else if (lowerFileName.endsWith(".png")) {
+ return "image/png";
+ } else if (lowerFileName.endsWith(".txt")) {
+ return "text/plain";
+ } else if (lowerFileName.endsWith(".doc")) {
+ return "application/msword";
+ } else if (lowerFileName.endsWith(".docx")) {
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ }
+ return "application/octet-stream"; // Default binary
+ }
+
+ public String getRootFolderId() {
+ return rootFolderId;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/BsK/server/service/GoogleDriveServiceOAuth.java b/src/main/java/BsK/server/service/GoogleDriveServiceOAuth.java
new file mode 100644
index 0000000..066d309
--- /dev/null
+++ b/src/main/java/BsK/server/service/GoogleDriveServiceOAuth.java
@@ -0,0 +1,266 @@
+package BsK.server.service;
+
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
+import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
+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.FileContent;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.gson.GsonFactory;
+import com.google.api.client.util.store.FileDataStoreFactory;
+import com.google.api.services.drive.Drive;
+import com.google.api.services.drive.DriveScopes;
+import com.google.api.services.drive.model.File;
+import com.google.api.services.drive.model.FileList;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@Slf4j
+public class GoogleDriveServiceOAuth {
+ private static final String APPLICATION_NAME = "BSK Clinic Management System";
+ private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
+ private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE);
+
+ // Path to OAuth2 client credentials file
+ private static final String OAUTH_CREDENTIALS_PATH = "src/main/resources/google-oauth-credentials.json";
+ // Directory to store authorization tokens
+ private static final String TOKENS_DIRECTORY_PATH = "tokens";
+
+ private Drive driveService;
+ private String rootFolderId; // BSK Clinic root folder
+
+ public GoogleDriveServiceOAuth() {
+ try {
+ this.driveService = buildDriveService();
+ this.rootFolderId = getOrCreateRootFolder();
+ log.info("Google Drive OAuth service initialized successfully");
+ } catch (Exception e) {
+ log.error("Failed to initialize Google Drive OAuth service", e);
+ throw new RuntimeException("Google Drive OAuth service initialization failed", e);
+ }
+ }
+
+ private Drive buildDriveService() throws GeneralSecurityException, IOException {
+ // Load client secrets
+ GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
+ JSON_FACTORY,
+ new InputStreamReader(new FileInputStream(OAUTH_CREDENTIALS_PATH))
+ );
+
+ // Build flow and trigger user authorization request
+ GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
+ GoogleNetHttpTransport.newTrustedTransport(),
+ JSON_FACTORY,
+ clientSecrets,
+ SCOPES)
+ .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
+ .setAccessType("offline")
+ .build();
+
+ LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
+ Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
+
+ // Build a new authorized API client service
+ return new Drive.Builder(
+ GoogleNetHttpTransport.newTrustedTransport(),
+ JSON_FACTORY,
+ credential)
+ .setApplicationName(APPLICATION_NAME)
+ .build();
+ }
+
+ /**
+ * Get or create the root folder for BSK Clinic
+ */
+ private String getOrCreateRootFolder() throws IOException {
+ String folderName = "BSK_Clinic_Patient_Files";
+
+ // Search for existing root folder
+ String query = "name='" + folderName + "' and mimeType='application/vnd.google-apps.folder' and trashed=false";
+ FileList result = driveService.files().list()
+ .setQ(query)
+ .setSpaces("drive")
+ .execute();
+
+ List files = result.getFiles();
+ if (files != null && !files.isEmpty()) {
+ String folderId = files.get(0).getId();
+ log.info("Found existing root folder: {} (ID: {})", folderName, folderId);
+ return folderId;
+ }
+
+ // Create root folder if it doesn't exist
+ File folderMetadata = new File();
+ folderMetadata.setName(folderName);
+ folderMetadata.setMimeType("application/vnd.google-apps.folder");
+
+ File folder = driveService.files().create(folderMetadata)
+ .setFields("id")
+ .execute();
+
+ log.info("Created new root folder: {} (ID: {})", folderName, folder.getId());
+ return folder.getId();
+ }
+
+ /**
+ * Create a folder for a specific patient
+ */
+ public String createPatientFolder(String patientId, String patientName) throws IOException {
+ String folderName = "Patient_" + patientId + "_" + sanitizeFileName(patientName);
+
+ // Check if folder already exists
+ Optional existingFolderId = findPatientFolder(patientId);
+ if (existingFolderId.isPresent()) {
+ log.info("Patient folder already exists: {} (ID: {})", folderName, existingFolderId.get());
+ return existingFolderId.get();
+ }
+
+ // Create new patient folder
+ File folderMetadata = new File();
+ folderMetadata.setName(folderName);
+ folderMetadata.setMimeType("application/vnd.google-apps.folder");
+ folderMetadata.setParents(Collections.singletonList(rootFolderId));
+
+ File folder = driveService.files().create(folderMetadata)
+ .setFields("id")
+ .execute();
+
+ log.info("Created patient folder: {} (ID: {})", folderName, folder.getId());
+ return folder.getId();
+ }
+
+ /**
+ * Find existing patient folder by patient ID
+ */
+ private Optional findPatientFolder(String patientId) throws IOException {
+ String query = "name contains 'Patient_" + patientId + "_' and mimeType='application/vnd.google-apps.folder' and trashed=false and '" + rootFolderId + "' in parents";
+
+ FileList result = driveService.files().list()
+ .setQ(query)
+ .setSpaces("drive")
+ .execute();
+
+ List files = result.getFiles();
+ if (files != null && !files.isEmpty()) {
+ return Optional.of(files.get(0).getId());
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Upload a file to a patient's folder
+ */
+ public String uploadFile(String patientFolderId, java.io.File localFile, String fileName) throws IOException {
+ String mimeType = determineMimeType(fileName);
+
+ File fileMetadata = new File();
+ fileMetadata.setName(fileName);
+ fileMetadata.setParents(Collections.singletonList(patientFolderId));
+
+ FileContent mediaContent = new FileContent(mimeType, localFile);
+
+ File uploadedFile = driveService.files().create(fileMetadata, mediaContent)
+ .setFields("id,webViewLink,webContentLink")
+ .execute();
+
+ log.info("Uploaded file: {} (ID: {})", fileName, uploadedFile.getId());
+ return uploadedFile.getId();
+ }
+
+ /**
+ * Get the public sharing URL for a folder
+ */
+ public String getFolderSharingUrl(String folderId) throws IOException {
+ // Make the folder publicly viewable
+ com.google.api.services.drive.model.Permission permission = new com.google.api.services.drive.model.Permission();
+ permission.setType("anyone");
+ permission.setRole("reader");
+
+ driveService.permissions().create(folderId, permission).execute();
+
+ // Get the folder details with web view link
+ File folder = driveService.files().get(folderId)
+ .setFields("webViewLink")
+ .execute();
+
+ return folder.getWebViewLink();
+ }
+
+ /**
+ * Create a test folder and upload sample files
+ */
+ public void runConnectionTest() throws IOException {
+ log.info("Starting Google Drive OAuth connection test...");
+
+ // Create test folder
+ String testFolderId = createPatientFolder("TEST002", "OAuth Test Patient");
+
+ // Create a test text file
+ java.io.File testFile = new java.io.File("test-oauth-connection.txt");
+ try {
+ java.io.FileWriter writer = new java.io.FileWriter(testFile);
+ writer.write("BSK Clinic - Google Drive OAuth Connection Test\n");
+ writer.write("Timestamp: " + java.time.LocalDateTime.now() + "\n");
+ writer.write("This file verifies that the Google Drive OAuth integration is working correctly.\n");
+ writer.write("Authentication method: OAuth2 with user consent\n");
+ writer.close();
+
+ // Upload the test file
+ String fileId = uploadFile(testFolderId, testFile, "oauth-connection-test.txt");
+
+ // Get sharing URL
+ String folderUrl = getFolderSharingUrl(testFolderId);
+
+ log.info("✅ Google Drive OAuth connection test successful!");
+ log.info("📁 Test folder URL: {}", folderUrl);
+ log.info("📄 Test file ID: {}", fileId);
+
+ // Clean up test file
+ testFile.delete();
+
+ } catch (Exception e) {
+ log.error("❌ Google Drive OAuth connection test failed", e);
+ if (testFile.exists()) {
+ testFile.delete();
+ }
+ throw e;
+ }
+ }
+
+ private String sanitizeFileName(String fileName) {
+ // Remove or replace invalid characters for file names
+ return fileName.replaceAll("[^a-zA-Z0-9._-]", "_");
+ }
+
+ private String determineMimeType(String fileName) {
+ String lowerFileName = fileName.toLowerCase();
+ if (lowerFileName.endsWith(".pdf")) {
+ return "application/pdf";
+ } else if (lowerFileName.endsWith(".jpg") || lowerFileName.endsWith(".jpeg")) {
+ return "image/jpeg";
+ } else if (lowerFileName.endsWith(".png")) {
+ return "image/png";
+ } else if (lowerFileName.endsWith(".txt")) {
+ return "text/plain";
+ } else if (lowerFileName.endsWith(".doc")) {
+ return "application/msword";
+ } else if (lowerFileName.endsWith(".docx")) {
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ }
+ return "application/octet-stream"; // Default binary
+ }
+
+ public String getRootFolderId() {
+ return rootFolderId;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/BsK/server/test/GoogleDriveOAuthTest.java b/src/main/java/BsK/server/test/GoogleDriveOAuthTest.java
new file mode 100644
index 0000000..e693b8a
--- /dev/null
+++ b/src/main/java/BsK/server/test/GoogleDriveOAuthTest.java
@@ -0,0 +1,125 @@
+package BsK.server.test;
+
+import BsK.server.service.GoogleDriveServiceOAuth;
+import BsK.server.util.QRCodeGenerator;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+@Slf4j
+public class GoogleDriveOAuthTest {
+
+ public static void main(String[] args) {
+ log.info("🚀 Starting BSK Google Drive OAuth Integration Test");
+
+ try {
+ // Initialize Google Drive OAuth service
+ log.info("🔐 This will open a browser window for Google authentication...");
+ GoogleDriveServiceOAuth driveService = new GoogleDriveServiceOAuth();
+
+ // Run basic connection test
+ driveService.runConnectionTest();
+
+ // Test patient folder creation and file upload
+ testPatientFileOperations(driveService);
+
+ // Test QR code generation
+ testQRCodeGeneration();
+
+ log.info("🎉 All Google Drive OAuth tests completed successfully!");
+
+ } catch (Exception e) {
+ log.error("❌ Google Drive OAuth test failed", e);
+ System.exit(1);
+ }
+ }
+
+ private static void testPatientFileOperations(GoogleDriveServiceOAuth driveService) throws IOException {
+ log.info("🧪 Testing patient file operations...");
+
+ // Test patient folder creation
+ String patientId = "P002";
+ String patientName = "Trần Thị B";
+ String patientFolderId = driveService.createPatientFolder(patientId, patientName);
+
+ // Create sample PDF file (mock)
+ File testPdf = createSamplePDF("medical_report_" + patientId + ".pdf");
+
+ // Create sample image file (mock)
+ File testImage = createSampleTextFile("xray_" + patientId + ".txt", "Sample X-ray data for patient " + patientId);
+
+ try {
+ // Upload files
+ String pdfFileId = driveService.uploadFile(patientFolderId, testPdf, testPdf.getName());
+ String imageFileId = driveService.uploadFile(patientFolderId, testImage, "xray_" + patientId + ".jpg");
+
+ // Get folder sharing URL
+ String folderUrl = driveService.getFolderSharingUrl(patientFolderId);
+
+ log.info("✅ Patient folder created successfully");
+ log.info("📁 Patient folder URL: {}", folderUrl);
+ log.info("📄 PDF file ID: {}", pdfFileId);
+ log.info("🖼️ Image file ID: {}", imageFileId);
+
+ // Test QR code generation for this folder
+ testQRCodeForFolder(folderUrl, patientId);
+
+ } finally {
+ // Clean up test files
+ if (testPdf.exists()) testPdf.delete();
+ if (testImage.exists()) testImage.delete();
+ }
+ }
+
+ private static void testQRCodeGeneration() {
+ log.info("🧪 Testing QR code generation...");
+ QRCodeGenerator.testQRGeneration();
+ }
+
+ private static void testQRCodeForFolder(String folderUrl, String patientId) {
+ try {
+ log.info("🧪 Testing QR code generation for patient folder...");
+ String qrFileName = "patient_" + patientId + "_qr.png";
+ File qrFile = QRCodeGenerator.generateQRCode(folderUrl, qrFileName);
+
+ log.info("✅ QR code generated for patient folder: {}", qrFile.getAbsolutePath());
+ log.info("📱 QR code contains URL: {}", folderUrl);
+
+ // Clean up
+ if (qrFile.exists()) {
+ qrFile.delete();
+ log.info("🧹 QR code file cleaned up");
+ }
+
+ } catch (Exception e) {
+ log.error("❌ QR code generation failed for patient folder", e);
+ }
+ }
+
+ private static File createSamplePDF(String fileName) throws IOException {
+ File file = new File(fileName);
+ FileWriter writer = new FileWriter(file);
+ writer.write("BSK Clinic - Sample Medical Report (OAuth Test)\n");
+ writer.write("==========================================\n\n");
+ writer.write("Patient ID: P002\n");
+ writer.write("Patient Name: Trần Thị B\n");
+ writer.write("Date: " + java.time.LocalDate.now() + "\n\n");
+ writer.write("This is a sample medical report for testing Google Drive OAuth integration.\n");
+ writer.write("In production, this would be a properly formatted PDF report.\n");
+ writer.write("Authentication method: OAuth2 with user consent\n");
+ writer.close();
+ return file;
+ }
+
+ private static File createSampleTextFile(String fileName, String content) throws IOException {
+ File file = new File(fileName);
+ FileWriter writer = new FileWriter(file);
+ writer.write(content);
+ writer.write("\nCreated: " + java.time.LocalDateTime.now());
+ writer.write("\nAuthentication method: OAuth2 with user consent");
+ writer.close();
+ return file;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/BsK/server/test/GoogleDriveTest.java b/src/main/java/BsK/server/test/GoogleDriveTest.java
new file mode 100644
index 0000000..664c92d
--- /dev/null
+++ b/src/main/java/BsK/server/test/GoogleDriveTest.java
@@ -0,0 +1,122 @@
+package BsK.server.test;
+
+import BsK.server.service.GoogleDriveService;
+import BsK.server.util.QRCodeGenerator;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+@Slf4j
+public class GoogleDriveTest {
+
+ public static void main(String[] args) {
+ log.info("🚀 Starting BSK Google Drive Integration Test");
+
+ try {
+ // Initialize Google Drive service
+ GoogleDriveService driveService = new GoogleDriveService();
+
+ // Run basic connection test
+ driveService.runConnectionTest();
+
+ // Test patient folder creation and file upload
+ testPatientFileOperations(driveService);
+
+ // Test QR code generation
+ testQRCodeGeneration();
+
+ log.info("🎉 All Google Drive tests completed successfully!");
+
+ } catch (Exception e) {
+ log.error("❌ Google Drive test failed", e);
+ System.exit(1);
+ }
+ }
+
+ private static void testPatientFileOperations(GoogleDriveService driveService) throws IOException {
+ log.info("🧪 Testing patient file operations...");
+
+ // Test patient folder creation
+ String patientId = "P001";
+ String patientName = "Nguyễn Văn A";
+ String patientFolderId = driveService.createPatientFolder(patientId, patientName);
+
+ // Create sample PDF file (mock)
+ File testPdf = createSamplePDF("sample_report_" + patientId + ".pdf");
+
+ // Create sample image file (mock)
+ File testImage = createSampleTextFile("ultrasound_" + patientId + ".txt", "Sample ultrasound data for patient " + patientId);
+
+ try {
+ // Upload files
+ String pdfFileId = driveService.uploadFile(patientFolderId, testPdf, testPdf.getName());
+ String imageFileId = driveService.uploadFile(patientFolderId, testImage, "ultrasound_" + patientId + ".jpg");
+
+ // Get folder sharing URL
+ String folderUrl = driveService.getFolderSharingUrl(patientFolderId);
+
+ log.info("✅ Patient folder created successfully");
+ log.info("📁 Patient folder URL: {}", folderUrl);
+ log.info("📄 PDF file ID: {}", pdfFileId);
+ log.info("🖼️ Image file ID: {}", imageFileId);
+
+ // Test QR code generation for this folder
+ testQRCodeForFolder(folderUrl, patientId);
+
+ } finally {
+ // Clean up test files
+ if (testPdf.exists()) testPdf.delete();
+ if (testImage.exists()) testImage.delete();
+ }
+ }
+
+ private static void testQRCodeGeneration() {
+ log.info("🧪 Testing QR code generation...");
+ QRCodeGenerator.testQRGeneration();
+ }
+
+ private static void testQRCodeForFolder(String folderUrl, String patientId) {
+ try {
+ log.info("🧪 Testing QR code generation for patient folder...");
+ String qrFileName = "patient_" + patientId + "_qr.png";
+ File qrFile = QRCodeGenerator.generateQRCode(folderUrl, qrFileName);
+
+ log.info("✅ QR code generated for patient folder: {}", qrFile.getAbsolutePath());
+ log.info("📱 QR code contains URL: {}", folderUrl);
+
+ // Clean up
+ if (qrFile.exists()) {
+ qrFile.delete();
+ log.info("🧹 QR code file cleaned up");
+ }
+
+ } catch (Exception e) {
+ log.error("❌ QR code generation failed for patient folder", e);
+ }
+ }
+
+ private static File createSamplePDF(String fileName) throws IOException {
+ File file = new File(fileName);
+ FileWriter writer = new FileWriter(file);
+ writer.write("BSK Clinic - Sample Medical Report\n");
+ writer.write("=================================\n\n");
+ writer.write("Patient ID: P001\n");
+ writer.write("Patient Name: Nguyễn Văn A\n");
+ writer.write("Date: " + java.time.LocalDate.now() + "\n\n");
+ writer.write("This is a sample medical report for testing Google Drive integration.\n");
+ writer.write("In production, this would be a properly formatted PDF report.\n");
+ writer.close();
+ return file;
+ }
+
+ private static File createSampleTextFile(String fileName, String content) throws IOException {
+ File file = new File(fileName);
+ FileWriter writer = new FileWriter(file);
+ writer.write(content);
+ writer.write("\nCreated: " + java.time.LocalDateTime.now());
+ writer.close();
+ return file;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/BsK/server/util/QRCodeGenerator.java b/src/main/java/BsK/server/util/QRCodeGenerator.java
new file mode 100644
index 0000000..d36f355
--- /dev/null
+++ b/src/main/java/BsK/server/util/QRCodeGenerator.java
@@ -0,0 +1,111 @@
+package BsK.server.util;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+@Slf4j
+public class QRCodeGenerator {
+
+ private static final int DEFAULT_WIDTH = 200;
+ private static final int DEFAULT_HEIGHT = 200;
+ private static final int BLACK = 0xFF000000;
+ private static final int WHITE = 0xFFFFFFFF;
+
+ /**
+ * Generate QR code for a given URL and save as image file
+ */
+ public static File generateQRCode(String url, String fileName) throws WriterException, IOException {
+ return generateQRCode(url, fileName, DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ }
+
+ /**
+ * Generate QR code with custom dimensions
+ */
+ public static File generateQRCode(String url, String fileName, int width, int height) throws WriterException, IOException {
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, width, height);
+
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? BLACK : WHITE);
+ }
+ }
+
+ File qrCodeFile = new File(fileName);
+ ImageIO.write(bufferedImage, "PNG", qrCodeFile);
+
+ log.info("QR code generated: {} for URL: {}", fileName, url);
+ return qrCodeFile;
+ }
+
+ /**
+ * Generate QR code as BufferedImage (for embedding in reports)
+ */
+ public static BufferedImage generateQRCodeImage(String url, int width, int height) throws WriterException {
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, width, height);
+
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? BLACK : WHITE);
+ }
+ }
+
+ return bufferedImage;
+ }
+
+ /**
+ * Generate QR code with custom colors
+ */
+ public static BufferedImage generateQRCodeImage(String url, int width, int height, Color foregroundColor, Color backgroundColor) throws WriterException {
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, width, height);
+
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ int foregroundRGB = foregroundColor.getRGB();
+ int backgroundRGB = backgroundColor.getRGB();
+
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? foregroundRGB : backgroundRGB);
+ }
+ }
+
+ return bufferedImage;
+ }
+
+ /**
+ * Test method to generate a sample QR code
+ */
+ public static void testQRGeneration() {
+ try {
+ String testUrl = "https://drive.google.com/drive/folders/test123";
+ String fileName = "test-qr-code.png";
+
+ File qrFile = generateQRCode(testUrl, fileName);
+ log.info("✅ Test QR code generated successfully: {}", qrFile.getAbsolutePath());
+
+ // Clean up test file
+ if (qrFile.exists()) {
+ qrFile.delete();
+ log.info("🧹 Test QR code file cleaned up");
+ }
+
+ } catch (Exception e) {
+ log.error("❌ QR code generation test failed", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/database/BSK.db b/src/main/resources/database/BSK.db
index ed25376..2619708 100644
Binary files a/src/main/resources/database/BSK.db and b/src/main/resources/database/BSK.db differ