From dc619f5b6921c87dc545d56a6d81c420118b2d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:13:02 +0700 Subject: [PATCH] [update] Implement Google Drive integration for patient and checkup folder management. Add functionality to create and upload files to Google Drive folders for patients and checkups. Enhance server configuration to support Google Drive settings, including root folder name customization. Update UI components to reflect Google Drive status and provide retry options for connection issues. --- .gitignore | 7 +- .vscode/launch.json | 7 + .../java/BsK/common/util/text/TextUtils.java | 116 +++++++ src/main/java/BsK/server/Server.java | 151 +++++++++ src/main/java/BsK/server/ServerDashboard.java | 123 ++++++- .../server/network/handler/ServerHandler.java | 310 +++++++++++++++++- .../server/service/GoogleDriveService.java | 243 -------------- .../service/GoogleDriveServiceOAuth.java | 170 +++++++++- .../BsK/server/test/GoogleDriveOAuthTest.java | 212 ++++++++---- .../java/BsK/server/test/GoogleDriveTest.java | 122 ------- .../java/BsK/server/util/QRCodeGenerator.java | 10 +- src/main/resources/config.properties | 14 +- src/main/resources/database/BSK.db | Bin 1196032 -> 1196032 bytes 13 files changed, 1045 insertions(+), 440 deletions(-) delete mode 100644 src/main/java/BsK/server/service/GoogleDriveService.java delete mode 100644 src/main/java/BsK/server/test/GoogleDriveTest.java diff --git a/.gitignore b/.gitignore index ba182bd..48430b1 100644 --- a/.gitignore +++ b/.gitignore @@ -149,7 +149,7 @@ src/main/resources/image/checkup_media/32/IMG_32_20250604_112123.png src/main/resources/image/checkup_media/34/IMG_34_20250604_120802.png 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/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 @@ -159,3 +159,8 @@ tokens/StoredCredential src/main/resources/database/BSK.db src/main/resources/database/BSK.db src/main/resources/database/BSK.db +GoogleDriveTest_README.md +patient_P002_checkup_CHK001_qr.png +patient_P002_checkup_CHK002_qr.png +test-qr-code.png +patient_P002_folder_qr.png diff --git a/.vscode/launch.json b/.vscode/launch.json index 56bde02..12cb1f6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,13 @@ { "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "TextUtils", + "request": "launch", + "mainClass": "BsK.common.util.text.TextUtils", + "projectName": "ldS_BsK" + }, { "type": "java", "name": "GoogleDriveOAuthTest", diff --git a/src/main/java/BsK/common/util/text/TextUtils.java b/src/main/java/BsK/common/util/text/TextUtils.java index 2507f24..d34d444 100644 --- a/src/main/java/BsK/common/util/text/TextUtils.java +++ b/src/main/java/BsK/common/util/text/TextUtils.java @@ -30,6 +30,97 @@ public final class TextUtils { return normalizedText; } + /** + * Converts Vietnamese patient name to English format suitable for file/folder names. + * Removes accents, special characters, and formats for use in Google Drive folder names. + * + * @param vietnameseName The Vietnamese patient name (e.g., "Trần Văn A", "Nguyễn Thị B") + * @return A sanitized English name suitable for folder names (e.g., "Tran_Van_A", "Nguyen_Thi_B") + */ + public static String vietnameseToEnglishName(String vietnameseName) { + if (vietnameseName == null || vietnameseName.trim().isEmpty()) { + return "Unknown_Patient"; + } + + // First remove accents + String englishName = removeAccents(vietnameseName.trim()); + + // Replace spaces with underscores + englishName = englishName.replaceAll("\\s+", "_"); + + // Remove any characters that are not letters, numbers, underscores, dots, or hyphens + englishName = englishName.replaceAll("[^a-zA-Z0-9._-]", ""); + + // Ensure it doesn't start or end with underscore + englishName = englishName.replaceAll("^_+|_+$", ""); + + // Replace multiple underscores with single underscore + englishName = englishName.replaceAll("_+", "_"); + + // If empty after sanitization, return default + if (englishName.isEmpty()) { + return "Unknown_Patient"; + } + + return englishName; + } + + /** + * Creates a standardized patient folder name for Google Drive. + * Format: "Patient_[ID]_[EnglishName]" + * + * @param patientId The patient ID + * @param patientLastName Patient's last name in Vietnamese + * @param patientFirstName Patient's first name in Vietnamese + * @return A standardized folder name (e.g., "Patient_123_Tran_Van_A") + */ + public static String createPatientFolderName(int patientId, String patientLastName, String patientFirstName) { + String fullName = (patientLastName + " " + patientFirstName).trim(); + String englishName = vietnameseToEnglishName(fullName); + return "Patient_" + patientId + "_" + englishName; + } + + /** + * Creates a standardized checkup folder name for Google Drive. + * Format: "Kham_benh_[YYYYMMDD]_[EnglishPatientName]" + * + * @param patientLastName Patient's last name in Vietnamese + * @param patientFirstName Patient's first name in Vietnamese + * @return A standardized checkup folder name (e.g., "Kham_benh_20241218_Tran_Van_A") + */ + public static String createCheckupFolderName(String patientLastName, String patientFirstName) { + // Get today's date in YYYYMMDD format + java.time.LocalDate today = java.time.LocalDate.now(); + String dateStr = today.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")); + + // Create English patient name + String fullName = (patientLastName + " " + patientFirstName).trim(); + String englishName = vietnameseToEnglishName(fullName); + + return "Kham_benh_" + dateStr + "_" + englishName; + } + + /** + * Creates a checkup folder name with specific checkup ID. + * Format: "Checkup_[ID]_[YYYYMMDD]_[EnglishPatientName]" + * + * @param checkupId The checkup ID + * @param patientLastName Patient's last name in Vietnamese + * @param patientFirstName Patient's first name in Vietnamese + * @return A standardized checkup folder name (e.g., "Checkup_123_20241218_Tran_Van_A") + */ + public static String createCheckupFolderNameWithId(int checkupId, String patientLastName, String patientFirstName) { + // Get today's date in YYYYMMDD format + java.time.LocalDate today = java.time.LocalDate.now(); + String dateStr = today.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")); + + // Create English patient name + String fullName = (patientLastName + " " + patientFirstName).trim(); + String englishName = vietnameseToEnglishName(fullName); + + return "Checkup_" + checkupId + "_" + dateStr + "_" + englishName; + } + /** * Scales down the font size definitions in an RTF string by half. * Finds all occurrences of \fsXX and replaces them with \fs(XX/2). @@ -84,6 +175,31 @@ public final class TextUtils { System.out.println("Original: " + word + " → Without Accents: " + TextUtils.removeAccents(word)); } + // Test Vietnamese to English name conversion + System.out.println("\n--- Testing Vietnamese Name Conversion ---"); + String[] testPatientNames = { + "Trần Văn A", "Nguyễn Thị B", "Lê Hoàng C", "Phạm Quốc Dũng", + "Bùi Thị Hạnh", "Đặng Minh Cường", "Võ Thế Vỹ", "Hoàng Gia Bảo" + }; + + for (String name : testPatientNames) { + String englishName = vietnameseToEnglishName(name); + String[] nameParts = name.split(" ", 2); + String lastName = nameParts[0]; + String firstName = nameParts.length > 1 ? nameParts[1] : ""; + + String patientFolderName = createPatientFolderName(123, lastName, firstName); + String checkupFolderName = createCheckupFolderName(lastName, firstName); + String checkupFolderWithId = createCheckupFolderNameWithId(456, lastName, firstName); + + System.out.println("Vietnamese: " + name); + System.out.println(" → English: " + englishName); + System.out.println(" → Patient Folder: " + patientFolderName); + System.out.println(" → Checkup Folder: " + checkupFolderName); + System.out.println(" → Checkup Folder (with ID): " + checkupFolderWithId); + System.out.println(); + } + // Test the new RTF scaling function System.out.println("\n--- Testing RTF Font Scaling ---"); String rtfTestString = "{\\rtf1\\ansi{\\fonttbl{\\f0\\fnil Monospaced;}}{\\colortbl;\\red0\\green0\\blue0;}\\pard\\sa200\\sl276\\slmult1\\cf1\\b\\f0\\fs48 Hello World\\fs24 now small\\par}"; diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index a0127a5..f6015d1 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -1,6 +1,7 @@ package BsK.server; import BsK.server.network.handler.ServerHandler; +import BsK.server.service.GoogleDriveServiceOAuth; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; @@ -36,6 +37,9 @@ import lombok.extern.slf4j.Slf4j; public class Server { private static int PORT; public static Connection connection; + public static GoogleDriveServiceOAuth googleDriveService; + private static boolean googleDriveEnabled = true; // Can be configured + private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files"; // Default static { try { @@ -81,14 +85,152 @@ public class Server { log.info("Using default port: {}", PORT); } + // Get Google Drive configuration + String driveEnabledStr = props.getProperty("google.drive.enabled"); + if (driveEnabledStr != null) { + googleDriveEnabled = Boolean.parseBoolean(driveEnabledStr); + } + + String driveRootFolderStr = props.getProperty("google.drive.root.folder"); + if (driveRootFolderStr != null && !driveRootFolderStr.trim().isEmpty()) { + googleDriveRootFolderName = driveRootFolderStr.trim(); + } + + log.info("Google Drive integration enabled: {}", googleDriveEnabled); + log.info("Google Drive root folder: {}", googleDriveRootFolderName); + // Directly use the provided database path String dbPath = "src/main/resources/database/BSK.db"; connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); + + // Initialize Google Drive service if enabled + initializeGoogleDriveService(); + } catch (Exception e) { throw new RuntimeException("Failed to initialize server", e); } } + private static void initializeGoogleDriveService() { + if (!googleDriveEnabled) { + log.info("Google Drive integration is disabled"); + googleDriveService = null; + return; + } + + try { + log.info("🔄 Initializing Google Drive OAuth service..."); + googleDriveService = new GoogleDriveServiceOAuth(googleDriveRootFolderName); + log.info("✅ Google Drive OAuth service initialized successfully"); + + // Update dashboard if it exists + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().updateGoogleDriveStatus(true, "Connected"); + } + + } catch (Exception e) { + log.error("❌ Failed to initialize Google Drive OAuth service: {}", e.getMessage()); + log.info("💡 Server will continue without Google Drive integration"); + log.info("🔧 To fix this, ensure google-oauth-credentials.json is properly configured"); + googleDriveService = null; + + // Update dashboard if it exists + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().updateGoogleDriveStatus(false, "Failed: " + e.getMessage()); + } + } + } + + /** + * Manually retry Google Drive connection + */ + public static void retryGoogleDriveConnection() { + log.info("🔄 Retrying Google Drive connection..."); + initializeGoogleDriveService(); + } + + /** + * Check if Google Drive is connected + */ + public static boolean isGoogleDriveConnected() { + return googleDriveService != null; + } + + /** + * Get Google Drive service instance + */ + public static GoogleDriveServiceOAuth getGoogleDriveService() { + return googleDriveService; + } + + /** + * Get the current Google Drive root folder name + */ + public static String getGoogleDriveRootFolderName() { + return googleDriveRootFolderName; + } + + /** + * Update Google Drive root folder name and reinitialize service + */ + public static void updateGoogleDriveRootFolder(String newRootFolderName) throws Exception { + if (newRootFolderName == null || newRootFolderName.trim().isEmpty()) { + throw new IllegalArgumentException("Root folder name cannot be empty"); + } + + googleDriveRootFolderName = newRootFolderName.trim(); + log.info("🔄 Updating Google Drive root folder to: {}", googleDriveRootFolderName); + + // Save to configuration file + saveGoogleDriveRootFolderToConfig(googleDriveRootFolderName); + + // Reinitialize Google Drive service with new root folder + if (googleDriveEnabled) { + initializeGoogleDriveService(); + } + } + + /** + * Save Google Drive root folder name to config file + */ + private static void saveGoogleDriveRootFolderToConfig(String rootFolderName) { + try { + Properties props = new Properties(); + + // Load existing properties + File externalConfig = new File("config/config.properties"); + if (externalConfig.exists()) { + try (var input = Files.newInputStream(externalConfig.toPath())) { + props.load(input); + } + } else { + try (InputStream input = Server.class.getClassLoader().getResourceAsStream("config.properties")) { + if (input != null) { + props.load(input); + } + } + } + + // Update the root folder property + props.setProperty("google.drive.root.folder", rootFolderName); + + // Create config directory if it doesn't exist + File configDir = new File("config"); + if (!configDir.exists()) { + configDir.mkdirs(); + } + + // Save to external config file + try (var output = Files.newOutputStream(externalConfig.toPath())) { + props.store(output, "BSK Server Configuration - Updated by Dashboard"); + log.info("💾 Saved Google Drive root folder setting to config file"); + } + + } catch (Exception e) { + log.warn("⚠️ Could not save Google Drive root folder to config file: {}", e.getMessage()); + } + } + private static String extractDatabaseFile() throws IOException { throw new UnsupportedOperationException("extractDatabaseFile is not used anymore. Database is read directly from the specified path."); } @@ -112,6 +254,10 @@ public class Server { dashboard.setVisible(true); dashboard.updateStatus("Starting...", Color.ORANGE); dashboard.updatePort(PORT); + + // Update Google Drive status on dashboard + dashboard.updateGoogleDriveStatus(isGoogleDriveConnected(), + isGoogleDriveConnected() ? "Connected" : "Disconnected"); EventLoopGroup parentGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); @@ -150,6 +296,11 @@ public class Server { // Update dashboard status to running dashboard.updateStatus("Running", Color.GREEN); dashboard.addLog("Server started successfully on port " + PORT); + if (isGoogleDriveConnected()) { + dashboard.addLog("Google Drive integration: Connected ✅"); + } else { + dashboard.addLog("Google Drive integration: Disconnected ❌"); + } f.channel().closeFuture().sync(); } catch (Exception e) { diff --git a/src/main/java/BsK/server/ServerDashboard.java b/src/main/java/BsK/server/ServerDashboard.java index d305d9a..6ad4ef0 100644 --- a/src/main/java/BsK/server/ServerDashboard.java +++ b/src/main/java/BsK/server/ServerDashboard.java @@ -23,6 +23,8 @@ public class ServerDashboard extends JFrame { private JLabel clientsLabel; private JLabel memoryLabel; private JLabel cpuLabel; + private JLabel googleDriveLabel; + private JButton googleDriveButton; private JToggleButton autoScrollToggle; private JTextField searchField; private static int connectedClients = 0; @@ -52,34 +54,56 @@ public class ServerDashboard extends JFrame { mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Status Panel (North) - JPanel topPanel = new JPanel(new GridLayout(2, 1, 0, 5)); + JPanel topPanel = new JPanel(new GridLayout(3, 1, 0, 5)); // Server Status Panel - JPanel statusPanel = new JPanel(new GridLayout(1, 3, 10, 0)); + JPanel statusPanel = new JPanel(new GridLayout(1, 4, 10, 0)); statusPanel.setBorder(BorderFactory.createTitledBorder("Server Status")); statusLabel = new JLabel("Status: Starting...", SwingConstants.CENTER); portLabel = new JLabel("Port: --", SwingConstants.CENTER); clientsLabel = new JLabel("Connected Clients: 0", SwingConstants.CENTER); + googleDriveLabel = new JLabel("Google Drive: Checking...", SwingConstants.CENTER); statusPanel.add(statusLabel); statusPanel.add(portLabel); statusPanel.add(clientsLabel); + statusPanel.add(googleDriveLabel); // System Info Panel - JPanel systemPanel = new JPanel(new GridLayout(1, 3, 10, 0)); + JPanel systemPanel = new JPanel(new GridLayout(1, 4, 10, 0)); systemPanel.setBorder(BorderFactory.createTitledBorder("System Information")); memoryLabel = new JLabel("Memory Usage: --", SwingConstants.CENTER); cpuLabel = new JLabel("CPU Usage: --", SwingConstants.CENTER); JLabel osLabel = new JLabel("OS: " + System.getProperty("os.name"), SwingConstants.CENTER); + + // Google Drive settings button + googleDriveButton = new JButton("Retry Drive"); + googleDriveButton.addActionListener(e -> retryGoogleDriveConnection()); + googleDriveButton.setToolTipText("Retry Google Drive connection"); systemPanel.add(memoryLabel); systemPanel.add(cpuLabel); systemPanel.add(osLabel); + systemPanel.add(googleDriveButton); + + // Google Drive Settings Panel + JPanel driveSettingsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + driveSettingsPanel.setBorder(BorderFactory.createTitledBorder("Google Drive Settings")); + + JLabel folderNameLabel = new JLabel("Root Folder Name:"); + JTextField driveRootFolderField = new JTextField(getCurrentDriveRootFolder(), 20); + JButton applyDriveSettingsButton = new JButton("Apply"); + applyDriveSettingsButton.addActionListener(e -> applyDriveSettings(driveRootFolderField.getText().trim())); + + driveSettingsPanel.add(folderNameLabel); + driveSettingsPanel.add(driveRootFolderField); + driveSettingsPanel.add(applyDriveSettingsButton); topPanel.add(statusPanel); topPanel.add(systemPanel); + topPanel.add(driveSettingsPanel); // Create split pane for logs and network info JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); @@ -304,8 +328,97 @@ public class ServerDashboard extends JFrame { }); } - @Override - public void dispose() { + public void updateGoogleDriveStatus(boolean connected, String statusMessage) { + SwingUtilities.invokeLater(() -> { + if (connected) { + googleDriveLabel.setText("Google Drive: ✅ " + statusMessage); + googleDriveLabel.setForeground(Color.GREEN); + googleDriveButton.setText("Test Drive"); + googleDriveButton.setToolTipText("Test Google Drive connection"); + } else { + googleDriveLabel.setText("Google Drive: ❌ " + statusMessage); + googleDriveLabel.setForeground(Color.RED); + googleDriveButton.setText("Retry Drive"); + googleDriveButton.setToolTipText("Retry Google Drive connection"); + } + }); + } + + private void retryGoogleDriveConnection() { + SwingUtilities.invokeLater(() -> { + googleDriveLabel.setText("Google Drive: 🔄 Connecting..."); + googleDriveLabel.setForeground(Color.ORANGE); + googleDriveButton.setEnabled(false); + addLog("Retrying Google Drive connection..."); + }); + + // Run connection attempt in background thread + new Thread(() -> { + try { + BsK.server.Server.retryGoogleDriveConnection(); + SwingUtilities.invokeLater(() -> { + googleDriveButton.setEnabled(true); + if (BsK.server.Server.isGoogleDriveConnected()) { + addLog("Google Drive connection successful ✅"); + } else { + addLog("Google Drive connection failed ❌"); + } + }); + } catch (Exception e) { + SwingUtilities.invokeLater(() -> { + googleDriveButton.setEnabled(true); + addLog("Google Drive connection error: " + e.getMessage()); + }); + } + }).start(); + } + + private String getCurrentDriveRootFolder() { + // Get current root folder name from Server or return default + try { + if (BsK.server.Server.getGoogleDriveService() != null) { + return BsK.server.Server.getGoogleDriveRootFolderName(); + } + } catch (Exception e) { + log.warn("Could not get current drive root folder name", e); + } + return "BSK_Clinic_Patient_Files"; // Default value + } + + private void applyDriveSettings(String newRootFolderName) { + if (newRootFolderName == null || newRootFolderName.trim().isEmpty()) { + addLog("❌ Root folder name cannot be empty"); + return; + } + + // Sanitize folder name + String sanitizedName = newRootFolderName.replaceAll("[^a-zA-Z0-9._-]", "_"); + if (!sanitizedName.equals(newRootFolderName)) { + addLog("⚠️ Folder name sanitized to: " + sanitizedName); + } + + addLog("🔄 Updating Google Drive root folder to: " + sanitizedName); + + new Thread(() -> { + try { + // Update the root folder name in server + BsK.server.Server.updateGoogleDriveRootFolder(sanitizedName); + + SwingUtilities.invokeLater(() -> { + addLog("✅ Google Drive root folder updated successfully"); + addLog("💡 New patient folders will be created under: " + sanitizedName); + }); + } catch (Exception e) { + SwingUtilities.invokeLater(() -> { + addLog("❌ Failed to update Google Drive root folder: " + e.getMessage()); + }); + log.error("Error updating Google Drive root folder", e); + } + }).start(); + } + + @Override + public void dispose() { if (statsTimer != null) { statsTimer.stop(); } diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index a881f59..2771b03 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -42,6 +42,7 @@ import BsK.common.entity.Medicine; import BsK.common.entity.Service; import BsK.common.entity.PatientHistory; import BsK.common.entity.Template; +import BsK.common.util.text.TextUtils; import static BsK.server.Server.statement; @@ -636,7 +637,11 @@ public class ServerHandler extends SimpleChannelInboundHandler { + try { + // Check if Google Drive is connected + if (!Server.isGoogleDriveConnected()) { + log.info("Google Drive not connected - skipping folder creation for patient {}", patientId); + return; + } + + log.info("Creating Google Drive folder for patient {} ({} {})", patientId, patientLastName, patientFirstName); + + // Create English name (without Patient_ prefix since createPatientFolder will add it) + String fullName = (patientLastName + " " + patientFirstName).trim(); + String englishPatientName = TextUtils.vietnameseToEnglishName(fullName); + + // Create patient folder in Google Drive + String patientFolderId = Server.getGoogleDriveService().createPatientFolder( + String.valueOf(patientId), + englishPatientName + ); + + // Make the folder public and get sharing URL + String folderUrl = Server.getGoogleDriveService().getFolderSharingUrl(patientFolderId); + + // Update both drive_url and drive_folder_id in database + updatePatientDriveInfo(patientId, folderUrl, patientFolderId); + + log.info("Google Drive folder created successfully for patient {}: {}", patientId, folderUrl); + + // Notify dashboard + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Created Google Drive folder for patient %d: %s", patientId, englishPatientName) + ); + } + + } catch (Exception e) { + log.error("Failed to create Google Drive folder for patient {}: {}", patientId, e.getMessage()); + + // Notify dashboard about the error + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Failed to create Google Drive folder for patient %d: %s", patientId, e.getMessage()) + ); + } + } + }).start(); + } + + /** + * Updates both the drive_url and drive_folder_id columns for a patient in the database. + */ + private void updatePatientDriveInfo(int patientId, String driveUrl, String driveFolderId) { + try { + PreparedStatement updateStmt = Server.connection.prepareStatement( + "UPDATE Customer SET drive_url = ?, drive_folder_id = ? WHERE customer_id = ?" + ); + updateStmt.setString(1, driveUrl); + updateStmt.setString(2, driveFolderId); + updateStmt.setInt(3, patientId); + + int rowsUpdated = updateStmt.executeUpdate(); + updateStmt.close(); + + if (rowsUpdated > 0) { + log.info("Updated Google Drive info for patient {}: URL={}, FolderID={}", patientId, driveUrl, driveFolderId); + } else { + log.warn("No rows updated for patient {} drive info", patientId); + } + + } catch (SQLException e) { + log.error("Failed to update Google Drive info for patient {}: {}", patientId, e.getMessage()); + } + } + + /** + * Creates a Google Drive folder for a checkup asynchronously and updates the checkup drive_url in database. + * This method runs in a background thread to avoid blocking the checkup creation response. + */ + private void createCheckupGoogleDriveFolderAsync(int checkupId, int customerId) { + // Run in background thread to avoid blocking + new Thread(() -> { + try { + // Check if Google Drive is connected + if (!Server.isGoogleDriveConnected()) { + log.info("Google Drive not connected - skipping checkup folder creation for checkup {}", checkupId); + return; + } + + // Get patient information from database + PreparedStatement patientStmt = Server.connection.prepareStatement( + "SELECT customer_last_name, customer_first_name, drive_folder_id FROM Customer WHERE customer_id = ?" + ); + patientStmt.setInt(1, customerId); + ResultSet patientRs = patientStmt.executeQuery(); + + String patientLastName = ""; + String patientFirstName = ""; + String patientDriveFolderId = ""; + + if (patientRs.next()) { + patientLastName = patientRs.getString("customer_last_name"); + patientFirstName = patientRs.getString("customer_first_name"); + patientDriveFolderId = patientRs.getString("drive_folder_id"); + } + patientStmt.close(); + patientRs.close(); + + if (patientDriveFolderId == null || patientDriveFolderId.trim().isEmpty()) { + log.warn("Patient {} has no Google Drive folder ID - cannot create checkup folder", customerId); + return; + } + + log.info("Creating Google Drive checkup folder for checkup {} (patient: {} {})", checkupId, patientLastName, patientFirstName); + + // Create checkup folder name using Vietnamese format + String checkupFolderName = TextUtils.createCheckupFolderNameWithId(checkupId, patientLastName, patientFirstName); + + // Create checkup folder directly under patient folder using saved folder ID + String checkupFolderId = createCheckupFolderDirectly(patientDriveFolderId, checkupFolderName); + + // Make the checkup folder public and get sharing URL + String folderUrl = Server.getGoogleDriveService().getFolderSharingUrl(checkupFolderId); + + // Update the checkup drive_url and drive_folder_id in database + updateCheckupDriveInfo(checkupId, folderUrl, checkupFolderId); + + log.info("Google Drive checkup folder created successfully for checkup {}: {}", checkupId, folderUrl); + + // Notify dashboard + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Created Google Drive checkup folder for checkup %d: %s", checkupId, checkupFolderName) + ); + } + + } catch (Exception e) { + log.error("Failed to create Google Drive checkup folder for checkup {}: {}", checkupId, e.getMessage()); + + // Notify dashboard about the error + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Failed to create Google Drive checkup folder for checkup %d: %s", checkupId, e.getMessage()) + ); + } + } + }).start(); + } + + /** + * Creates a checkup folder directly under patient folder using Google Drive API + */ + private String createCheckupFolderDirectly(String parentFolderId, String folderName) throws Exception { + return Server.getGoogleDriveService().createFolderUnderParent(parentFolderId, folderName); + } + + /** + * Updates both the drive_url and drive_folder_id columns for a checkup in the database. + * Note: You need to add drive_url and drive_folder_id columns to the Checkup table. + */ + private void updateCheckupDriveInfo(int checkupId, String driveUrl, String driveFolderId) { + try { + PreparedStatement updateStmt = Server.connection.prepareStatement( + "UPDATE Checkup SET drive_url = ?, drive_folder_id = ? WHERE checkup_id = ?" + ); + updateStmt.setString(1, driveUrl); + updateStmt.setString(2, driveFolderId); + updateStmt.setInt(3, checkupId); + + int rowsUpdated = updateStmt.executeUpdate(); + updateStmt.close(); + + if (rowsUpdated > 0) { + log.info("Updated Google Drive info for checkup {}: URL={}, FolderID={}", checkupId, driveUrl, driveFolderId); + } else { + log.warn("No rows updated for checkup {} drive info", checkupId); + } + + } catch (SQLException e) { + log.error("Failed to update Google Drive info for checkup {}: {}", checkupId, e.getMessage()); + } + } + + /** + * Uploads a checkup image to Google Drive asynchronously using the checkup's folder ID. + * This method runs in a background thread to avoid blocking the image upload response. + */ + private void uploadCheckupImageToGoogleDriveAsync(String checkupId, String fileName, byte[] imageData) { + // Run in background thread to avoid blocking + new Thread(() -> { + try { + // Check if Google Drive is connected + if (!Server.isGoogleDriveConnected()) { + log.info("Google Drive not connected - skipping image upload for checkup {}", checkupId); + return; + } + + // Get checkup's Google Drive folder ID from database + PreparedStatement checkupStmt = Server.connection.prepareStatement( + "SELECT drive_folder_id FROM Checkup WHERE checkup_id = ?" + ); + checkupStmt.setString(1, checkupId); + ResultSet checkupRs = checkupStmt.executeQuery(); + + String checkupDriveFolderId = ""; + if (checkupRs.next()) { + checkupDriveFolderId = checkupRs.getString("drive_folder_id"); + } + checkupStmt.close(); + checkupRs.close(); + + if (checkupDriveFolderId == null || checkupDriveFolderId.trim().isEmpty()) { + log.warn("Checkup {} has no Google Drive folder ID - cannot upload image", checkupId); + return; + } + + log.info("Uploading image {} to Google Drive for checkup {}", fileName, checkupId); + + // Create temporary file from byte array for Google Drive upload + java.io.File tempFile = createTempFileFromBytes(imageData, fileName); + + try { + // Upload file to Google Drive using checkup's folder ID + String uploadedFileId = Server.getGoogleDriveService().uploadFileToFolder( + checkupDriveFolderId, tempFile, fileName + ); + + log.info("Image uploaded successfully to Google Drive for checkup {}: FileID={}", checkupId, uploadedFileId); + + // Notify dashboard + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Uploaded image %s to Google Drive for checkup %s", fileName, checkupId) + ); + } + + } finally { + // Clean up temporary file + if (tempFile.exists()) { + tempFile.delete(); + log.debug("Cleaned up temporary file: {}", tempFile.getAbsolutePath()); + } + } + + } catch (Exception e) { + log.error("Failed to upload image to Google Drive for checkup {}: {}", checkupId, e.getMessage()); + + // Notify dashboard about the error + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Failed to upload image %s to Google Drive for checkup %s: %s", fileName, checkupId, e.getMessage()) + ); + } + } + }).start(); + } + + /** + * Creates a temporary file from byte array for Google Drive upload + */ + private java.io.File createTempFileFromBytes(byte[] data, String originalFileName) throws IOException { + // Get file extension + String extension = ""; + int dotIndex = originalFileName.lastIndexOf('.'); + if (dotIndex > 0) { + extension = originalFileName.substring(dotIndex); + } + + // Create temporary file + java.io.File tempFile = java.io.File.createTempFile("checkup_image_", extension); + + // Write byte data to temporary file + try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tempFile)) { + fos.write(data); + } + + return tempFile; + } } diff --git a/src/main/java/BsK/server/service/GoogleDriveService.java b/src/main/java/BsK/server/service/GoogleDriveService.java deleted file mode 100644 index ae7aeb2..0000000 --- a/src/main/java/BsK/server/service/GoogleDriveService.java +++ /dev/null @@ -1,243 +0,0 @@ -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 index 066d309..f69babf 100644 --- a/src/main/java/BsK/server/service/GoogleDriveServiceOAuth.java +++ b/src/main/java/BsK/server/service/GoogleDriveServiceOAuth.java @@ -37,12 +37,18 @@ public class GoogleDriveServiceOAuth { private Drive driveService; private String rootFolderId; // BSK Clinic root folder + private String rootFolderName; // Configurable root folder name public GoogleDriveServiceOAuth() { + this("BSK_Clinic_Patient_Files"); // Default folder name + } + + public GoogleDriveServiceOAuth(String rootFolderName) { try { + this.rootFolderName = rootFolderName != null ? rootFolderName : "BSK_Clinic_Patient_Files"; this.driveService = buildDriveService(); this.rootFolderId = getOrCreateRootFolder(); - log.info("Google Drive OAuth service initialized successfully"); + log.info("Google Drive OAuth service initialized successfully with root folder: {}", this.rootFolderName); } catch (Exception e) { log.error("Failed to initialize Google Drive OAuth service", e); throw new RuntimeException("Google Drive OAuth service initialization failed", e); @@ -82,7 +88,7 @@ public class GoogleDriveServiceOAuth { * Get or create the root folder for BSK Clinic */ private String getOrCreateRootFolder() throws IOException { - String folderName = "BSK_Clinic_Patient_Files"; + String folderName = this.rootFolderName; // Search for existing root folder String query = "name='" + folderName + "' and mimeType='application/vnd.google-apps.folder' and trashed=false"; @@ -157,6 +163,55 @@ public class GoogleDriveServiceOAuth { return Optional.empty(); } + /** + * Create a folder for a specific patient's checkup + */ + public String createCheckupFolder(String patientId, String patientName, String checkupId) throws IOException { + // First ensure patient folder exists + String patientFolderId = createPatientFolder(patientId, patientName); + + String checkupFolderName = "Checkup_" + checkupId; + + // Check if checkup folder already exists + Optional existingCheckupFolderId = findCheckupFolder(patientFolderId, checkupId); + if (existingCheckupFolderId.isPresent()) { + log.info("Checkup folder already exists: {} (ID: {})", checkupFolderName, existingCheckupFolderId.get()); + return existingCheckupFolderId.get(); + } + + // Create new checkup folder + File folderMetadata = new File(); + folderMetadata.setName(checkupFolderName); + folderMetadata.setMimeType("application/vnd.google-apps.folder"); + folderMetadata.setParents(Collections.singletonList(patientFolderId)); + + File folder = driveService.files().create(folderMetadata) + .setFields("id") + .execute(); + + log.info("Created checkup folder: {} (ID: {})", checkupFolderName, folder.getId()); + return folder.getId(); + } + + /** + * Find existing checkup folder by checkup ID within a patient folder + */ + private Optional findCheckupFolder(String patientFolderId, String checkupId) throws IOException { + String query = "name='Checkup_" + checkupId + "' and mimeType='application/vnd.google-apps.folder' and trashed=false and '" + patientFolderId + "' 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 */ @@ -177,6 +232,14 @@ public class GoogleDriveServiceOAuth { return uploadedFile.getId(); } + /** + * Upload a file to a specific checkup folder + */ + public String uploadFileToCheckup(String patientId, String patientName, String checkupId, java.io.File localFile, String fileName) throws IOException { + String checkupFolderId = createCheckupFolder(patientId, patientName, checkupId); + return uploadFile(checkupFolderId, localFile, fileName); + } + /** * Get the public sharing URL for a folder */ @@ -195,6 +258,57 @@ public class GoogleDriveServiceOAuth { return folder.getWebViewLink(); } + + /** + * Get the folder URL (without making it public) + */ + public String getFolderUrl(String folderId) throws IOException { + File folder = driveService.files().get(folderId) + .setFields("webViewLink") + .execute(); + + return folder.getWebViewLink(); + } + + /** + * Get patient folder URL with QR code generation + */ + public String getPatientFolderUrlWithQR(String patientId, String patientName) throws IOException { + String patientFolderId = createPatientFolder(patientId, patientName); + String folderUrl = getFolderUrl(patientFolderId); + + // Generate QR code for the folder URL + try { + String qrFileName = "patient_" + patientId + "_folder_qr.png"; + java.io.File qrFile = BsK.server.util.QRCodeGenerator.generateQRCode(folderUrl, qrFileName); + log.info("📱 QR code generated for patient {} folder: {}", patientId, qrFile.getAbsolutePath()); + log.info("🔗 QR code contains URL: {}", folderUrl); + return folderUrl; + } catch (Exception e) { + log.error("❌ Failed to generate QR code for patient folder", e); + return folderUrl; + } + } + + /** + * Get checkup folder URL with QR code generation + */ + public String getCheckupFolderUrlWithQR(String patientId, String patientName, String checkupId) throws IOException { + String checkupFolderId = createCheckupFolder(patientId, patientName, checkupId); + String folderUrl = getFolderUrl(checkupFolderId); + + // Generate QR code for the folder URL + try { + String qrFileName = "patient_" + patientId + "_checkup_" + checkupId + "_qr.png"; + java.io.File qrFile = BsK.server.util.QRCodeGenerator.generateQRCode(folderUrl, qrFileName); + log.info("📱 QR code generated for patient {} checkup {} folder: {}", patientId, checkupId, qrFile.getAbsolutePath()); + log.info("🔗 QR code contains URL: {}", folderUrl); + return folderUrl; + } catch (Exception e) { + log.error("❌ Failed to generate QR code for checkup folder", e); + return folderUrl; + } + } /** * Create a test folder and upload sample files @@ -263,4 +377,56 @@ public class GoogleDriveServiceOAuth { public String getRootFolderId() { return rootFolderId; } + + public String getRootFolderName() { + return rootFolderName; + } + + /** + * Update the root folder name (creates new folder, doesn't affect existing folders) + */ + public void updateRootFolderName(String newRootFolderName) throws IOException { + if (newRootFolderName != null && !newRootFolderName.trim().isEmpty()) { + this.rootFolderName = newRootFolderName.trim(); + this.rootFolderId = getOrCreateRootFolder(); + log.info("Root folder updated to: {} (ID: {})", this.rootFolderName, this.rootFolderId); + } + } + + /** + * Creates a folder directly under a specific parent folder using the folder ID + */ + public String createFolderUnderParent(String parentFolderId, String folderName) throws IOException { + File folderMetadata = new File(); + folderMetadata.setName(folderName); + folderMetadata.setMimeType("application/vnd.google-apps.folder"); + folderMetadata.setParents(Collections.singletonList(parentFolderId)); + + File folder = driveService.files().create(folderMetadata) + .setFields("id") + .execute(); + + log.info("Created folder: {} under parent {} (ID: {})", folderName, parentFolderId, folder.getId()); + return folder.getId(); + } + + /** + * Upload a file to a specific folder using the folder ID + */ + public String uploadFileToFolder(String folderId, java.io.File localFile, String fileName) throws IOException { + String mimeType = determineMimeType(fileName); + + File fileMetadata = new File(); + fileMetadata.setName(fileName); + fileMetadata.setParents(Collections.singletonList(folderId)); + + FileContent mediaContent = new FileContent(mimeType, localFile); + + File uploadedFile = driveService.files().create(fileMetadata, mediaContent) + .setFields("id,webViewLink,webContentLink") + .execute(); + + log.info("Uploaded file: {} to folder {} (File ID: {})", fileName, folderId, uploadedFile.getId()); + return uploadedFile.getId(); + } } \ 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 index e693b8a..af61451 100644 --- a/src/main/java/BsK/server/test/GoogleDriveOAuthTest.java +++ b/src/main/java/BsK/server/test/GoogleDriveOAuthTest.java @@ -13,6 +13,7 @@ public class GoogleDriveOAuthTest { public static void main(String[] args) { log.info("🚀 Starting BSK Google Drive OAuth Integration Test"); + log.info("📁 Testing folder structure: BSK App → Patient Folders → Checkup Folders → Files"); try { // Initialize Google Drive OAuth service @@ -22,13 +23,14 @@ public class GoogleDriveOAuthTest { // Run basic connection test driveService.runConnectionTest(); - // Test patient folder creation and file upload - testPatientFileOperations(driveService); + // Test new folder structure and file operations + testNewFolderStructure(driveService); - // Test QR code generation + // Test standalone QR code generation testQRCodeGeneration(); log.info("🎉 All Google Drive OAuth tests completed successfully!"); + log.info("📱 Check the generated QR code files in your project directory!"); } catch (Exception e) { log.error("❌ Google Drive OAuth test failed", e); @@ -36,89 +38,179 @@ public class GoogleDriveOAuthTest { } } - private static void testPatientFileOperations(GoogleDriveServiceOAuth driveService) throws IOException { - log.info("🧪 Testing patient file operations..."); + private static void testNewFolderStructure(GoogleDriveServiceOAuth driveService) throws IOException { + log.info("🧪 Testing BSK folder structure: App → Patient → Checkup → Files..."); - // Test patient folder creation + // Test data String patientId = "P002"; String patientName = "Trần Thị B"; - String patientFolderId = driveService.createPatientFolder(patientId, patientName); + String checkupId1 = "CHK001"; + String checkupId2 = "CHK002"; - // Create sample PDF file (mock) - File testPdf = createSamplePDF("medical_report_" + patientId + ".pdf"); + log.info("👤 Patient: {} (ID: {})", patientName, patientId); - // Create sample image file (mock) - File testImage = createSampleTextFile("xray_" + patientId + ".txt", "Sample X-ray data for patient " + patientId); + // Test Checkup 1: Medical examination with PDF report + log.info("🏥 Testing Checkup 1: Medical Examination (ID: {})", checkupId1); + + // Create sample files for checkup 1 + File medicalReport = createMedicalReportPDF(patientId, checkupId1); + File bloodTestImage = createBloodTestImage(patientId, checkupId1); try { - // Upload files - String pdfFileId = driveService.uploadFile(patientFolderId, testPdf, testPdf.getName()); - String imageFileId = driveService.uploadFile(patientFolderId, testImage, "xray_" + patientId + ".jpg"); + // Upload files to checkup 1 folder + String reportFileId = driveService.uploadFileToCheckup(patientId, patientName, checkupId1, medicalReport, "medical_report_" + checkupId1 + ".pdf"); + String imageFileId = driveService.uploadFileToCheckup(patientId, patientName, checkupId1, bloodTestImage, "blood_test_" + checkupId1 + ".jpg"); - // Get folder sharing URL - String folderUrl = driveService.getFolderSharingUrl(patientFolderId); + // Get checkup folder URL and generate QR code + String checkup1Url = driveService.getCheckupFolderUrlWithQR(patientId, patientName, checkupId1); - 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); + log.info("✅ Checkup 1 completed successfully"); + log.info("📄 Medical report file ID: {}", reportFileId); + log.info("🩸 Blood test image file ID: {}", imageFileId); + log.info("🔗 Checkup 1 folder URL: {}", checkup1Url); } finally { - // Clean up test files - if (testPdf.exists()) testPdf.delete(); - if (testImage.exists()) testImage.delete(); + // Clean up local test files + if (medicalReport.exists()) medicalReport.delete(); + if (bloodTestImage.exists()) bloodTestImage.delete(); } + + // Test Checkup 2: Ultrasound examination + log.info("🏥 Testing Checkup 2: Ultrasound Examination (ID: {})", checkupId2); + + // Create sample files for checkup 2 + File ultrasoundReport = createUltrasoundReportPDF(patientId, checkupId2); + File ultrasoundImage1 = createUltrasoundImage(patientId, checkupId2, "IMG001"); + File ultrasoundImage2 = createUltrasoundImage(patientId, checkupId2, "IMG002"); + + try { + // Upload files to checkup 2 folder + String reportFileId = driveService.uploadFileToCheckup(patientId, patientName, checkupId2, ultrasoundReport, "ultrasound_report_" + checkupId2 + ".pdf"); + String image1FileId = driveService.uploadFileToCheckup(patientId, patientName, checkupId2, ultrasoundImage1, "ultrasound_" + checkupId2 + "_IMG001.jpg"); + String image2FileId = driveService.uploadFileToCheckup(patientId, patientName, checkupId2, ultrasoundImage2, "ultrasound_" + checkupId2 + "_IMG002.jpg"); + + // Get checkup folder URL and generate QR code + String checkup2Url = driveService.getCheckupFolderUrlWithQR(patientId, patientName, checkupId2); + + log.info("✅ Checkup 2 completed successfully"); + log.info("📄 Ultrasound report file ID: {}", reportFileId); + log.info("🖼️ Ultrasound image 1 file ID: {}", image1FileId); + log.info("🖼️ Ultrasound image 2 file ID: {}", image2FileId); + log.info("🔗 Checkup 2 folder URL: {}", checkup2Url); + + } finally { + // Clean up local test files + if (ultrasoundReport.exists()) ultrasoundReport.delete(); + if (ultrasoundImage1.exists()) ultrasoundImage1.delete(); + if (ultrasoundImage2.exists()) ultrasoundImage2.delete(); + } + + // Get patient main folder URL and generate QR code + log.info("🧪 Testing patient main folder access..."); + String patientFolderUrl = driveService.getPatientFolderUrlWithQR(patientId, patientName); + log.info("👤 Patient main folder URL: {}", patientFolderUrl); + + // Summary + log.info("📊 Test Summary:"); + log.info(" 📁 BSK App Folder: Created/Verified"); + log.info(" 👤 Patient Folder: Patient_{}_{}",patientId, patientName.replace(" ", "_")); + log.info(" 🏥 Checkup Folders: Checkup_{} and Checkup_{}", checkupId1, checkupId2); + log.info(" 📄 Files uploaded: 5 files (2 PDFs + 3 images)"); + log.info(" 📱 QR codes generated: 3 codes (patient + 2 checkups)"); } private static void testQRCodeGeneration() { - log.info("🧪 Testing QR code generation..."); + log.info("🧪 Testing standalone QR code generation..."); QRCodeGenerator.testQRGeneration(); + log.info("✅ Standalone QR code test completed"); } - 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 { + private static File createMedicalReportPDF(String patientId, String checkupId) throws IOException { + String fileName = "medical_report_" + patientId + "_" + checkupId + ".pdf"; 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.write("BSK CLINIC - MEDICAL EXAMINATION REPORT\n"); + writer.write("=========================================\n\n"); + writer.write("Patient ID: " + patientId + "\n"); + writer.write("Checkup ID: " + checkupId + "\n"); + writer.write("Examination Date: " + java.time.LocalDate.now() + "\n"); + writer.write("Examination Time: " + java.time.LocalTime.now() + "\n\n"); + writer.write("EXAMINATION RESULTS:\n"); + writer.write("- Blood Pressure: 120/80 mmHg\n"); + writer.write("- Heart Rate: 72 bpm\n"); + writer.write("- Temperature: 36.5°C\n"); + writer.write("- Weight: 65 kg\n"); + writer.write("- Height: 165 cm\n\n"); + writer.write("DIAGNOSIS:\n"); + writer.write("Patient is in good health condition.\n\n"); + writer.write("RECOMMENDATIONS:\n"); + writer.write("- Regular exercise\n"); + writer.write("- Balanced diet\n"); + writer.write("- Follow-up in 6 months\n\n"); + writer.write("Doctor: Dr. Nguyen Van A\n"); + writer.write("Signature: _______________\n"); writer.close(); return file; } - private static File createSampleTextFile(String fileName, String content) throws IOException { + private static File createUltrasoundReportPDF(String patientId, String checkupId) throws IOException { + String fileName = "ultrasound_report_" + patientId + "_" + checkupId + ".pdf"; 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.write("BSK CLINIC - ULTRASOUND EXAMINATION REPORT\n"); + writer.write("==========================================\n\n"); + writer.write("Patient ID: " + patientId + "\n"); + writer.write("Checkup ID: " + checkupId + "\n"); + writer.write("Examination Date: " + java.time.LocalDate.now() + "\n"); + writer.write("Examination Time: " + java.time.LocalTime.now() + "\n\n"); + writer.write("ULTRASOUND FINDINGS:\n"); + writer.write("- Liver: Normal size and echogenicity\n"); + writer.write("- Gallbladder: Normal\n"); + writer.write("- Kidneys: Both kidneys normal\n"); + writer.write("- Spleen: Normal\n"); + writer.write("- Pancreas: Visualized portions normal\n\n"); + writer.write("CONCLUSION:\n"); + writer.write("Normal abdominal ultrasound examination.\n\n"); + writer.write("Images captured: 2 files\n"); + writer.write("Radiologist: Dr. Le Thi C\n"); + writer.write("Signature: _______________\n"); + writer.close(); + return file; + } + + private static File createBloodTestImage(String patientId, String checkupId) throws IOException { + String fileName = "blood_test_" + patientId + "_" + checkupId + ".txt"; + File file = new File(fileName); + FileWriter writer = new FileWriter(file); + + writer.write("BSK CLINIC - BLOOD TEST IMAGE DATA\n"); + writer.write("==================================\n\n"); + writer.write("Patient ID: " + patientId + "\n"); + writer.write("Checkup ID: " + checkupId + "\n"); + writer.write("Image Type: Blood test microscopy\n"); + writer.write("Capture Date: " + java.time.LocalDateTime.now() + "\n\n"); + writer.write("Note: This is a mock text file representing blood test image data.\n"); + writer.write("In production, this would be actual medical image files (JPG/PNG).\n"); + writer.close(); + return file; + } + + private static File createUltrasoundImage(String patientId, String checkupId, String imageId) throws IOException { + String fileName = "ultrasound_" + patientId + "_" + checkupId + "_" + imageId + ".txt"; + File file = new File(fileName); + FileWriter writer = new FileWriter(file); + + writer.write("BSK CLINIC - ULTRASOUND IMAGE DATA\n"); + writer.write("==================================\n\n"); + writer.write("Patient ID: " + patientId + "\n"); + writer.write("Checkup ID: " + checkupId + "\n"); + writer.write("Image ID: " + imageId + "\n"); + writer.write("Image Type: Abdominal ultrasound\n"); + writer.write("Capture Date: " + java.time.LocalDateTime.now() + "\n\n"); + writer.write("Note: This is a mock text file representing ultrasound image data.\n"); + writer.write("In production, this would be actual medical image files (JPG/PNG/DICOM).\n"); writer.close(); return file; } diff --git a/src/main/java/BsK/server/test/GoogleDriveTest.java b/src/main/java/BsK/server/test/GoogleDriveTest.java deleted file mode 100644 index 664c92d..0000000 --- a/src/main/java/BsK/server/test/GoogleDriveTest.java +++ /dev/null @@ -1,122 +0,0 @@ -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 index d36f355..252eadc 100644 --- a/src/main/java/BsK/server/util/QRCodeGenerator.java +++ b/src/main/java/BsK/server/util/QRCodeGenerator.java @@ -97,12 +97,12 @@ public class QRCodeGenerator { File qrFile = generateQRCode(testUrl, fileName); log.info("✅ Test QR code generated successfully: {}", qrFile.getAbsolutePath()); + log.info("📱 QR code contains URL: {}", testUrl); + log.info("💾 QR code saved as: {}", fileName); + log.info("ℹ️ You can scan this QR code to test the functionality"); - // Clean up test file - if (qrFile.exists()) { - qrFile.delete(); - log.info("🧹 Test QR code file cleaned up"); - } + // Keep the test file so user can see it + log.info("🔍 Test QR code file kept for inspection"); } catch (Exception e) { log.error("❌ QR code generation test failed", e); diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index d7c4e2f..613bd83 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -8,4 +8,16 @@ server.address=127.0.0.1 # The port number that the server will listen on and clients will connect to -server.port=1999 \ No newline at end of file +server.port=1999 + +# Google Drive Integration Settings +# -------------------------------- +# Set to true to enable Google Drive integration for patient file storage +# Requires google-oauth-credentials.json to be properly configured +# If disabled, the server will run without Google Drive features +google.drive.enabled=true + +# Root folder name in Google Drive where all patient folders will be created +# This folder will remain private, only individual patient folders will be shared +# Use alphanumeric characters, dots, dashes, and underscores only +google.drive.root.folder=BSK_Clinic_Patient_Files \ No newline at end of file diff --git a/src/main/resources/database/BSK.db b/src/main/resources/database/BSK.db index 2619708911517b2586e4625844c46625293806ed..66eff4a3858dc1fc9f84ed8851f1c3c45a86aa33 100644 GIT binary patch delta 1575 zcmdUwNlY787{}juypBEdJT^-m+hvStBZP@(Va%fBz-BNQV+hD8RRITY6l^17houKF zQj@eI3DF!#6h$qGdMK)*w5D|%(nD+19wJp;q^ecb2$WmWLnV$?q&~AnQ4h(rGx|yM z{_mSN-~W9x-z+YA78gA$X1WpptO{WV0N{wrORTu&{sjT|r3iCn$N zqmzjQBg_mUI1^zUOml+q#|rnJzvvhbCZasci?wWhEzdJ-LzCcY62*a7IL@5i*}Ra5 zF^O2=R)Xck1(xSTj%^TQ>GafelhYYWjh_pjh$fTK$?%D>WPD$6Mv{}EaBAAg^|J%6 z^x3(gkk8fVc4s(`*bzzBr%ro^M9%FEOujH6>(}UkdCzbL2&c@oSu5~GGjoOY1oDi@ z-+7?i_w1_UTwYj!X#g-=kPt5!Ilm_}BzQU26OUvyYIsny z3{VI)uaQ9NS)QlUszbR42=*GJc3%p^VI>5mdwv5zK-$-r`t*JU1Qn)Y(d|Ge1*BxT z+@cT5AuyLJjJ9eK2u36(F9-C)_?}4Hsw;qCMPkzxKmE4sKm^Bag0=YgUzgvc&tVXl zj0)Oz9H|vW_4<`qIlK*2a7hNY;dgKquEQnx75o{-bM)vSEH&2PHCStAI-QJ%QzN11 zNIE{Hk(QjTW(xsv6V{d-OD9uuT@7wM3L|pc&MF4iQesZ!Z=-PDLgakIU)aht+6f%K zulpW4s31j=cgd5wQ`!min)hkDvb`FV@?q7BMgPCmT%{(DYM$#_z9${i!aD%oho8ec z@E`aD7T_be3x9!szz1k^SD;cU2N-6#u;IiIG6as0BNPZFLWNKxGzbErMd%PDfST?QlbEu2DKfU zw3|qvR;!JuHC^LIK6r&#)jg&q8TfK_?J>QvkTqLM{}YSa&q1)jZml%?I+*UxLbj)6 zXY;(TlXbE$`w8_2+=K1oVSd`<9r7mXCIVAzSO5IH(B|syOh$XpaQzi8h{*3gp{XjP z@(rK>vw(V4SE*S5v-lR)j_ihr^B%9N+-#ODN2RMzTk;>38Ge^>4rZ@?jbu`j%s~5@ zfy42B_Zk@w?Wxu88hTXaRx9@2jPxUEmuh!z<(EqgZp`%TZszDHuEVUZ84M){)6W(w f#vDzXq}fL{Ir;;ixkc delta 594 zcmY+=Pe>F|90&0C=KqXu=I6}*V|QAc+*J^TRicn`54vtBEMZ}Rkhz;J3MISPdTv-` zldw?cuwI0sTbHbH8-k1?(1RDNV+0%Bv_rO+K%$-`fe$_pe*7MfFJIdpw(a31Q}-i; z<5UbG#8MxYyG~>O^XwxaJRLC{9Nuw{?C3us5+f=+K_ZkHsKX{y;VG2iBfNrLSI=I7 zfUlkH#HqPErNT^pcH-vLL}{idIORmjG!8D$H`EosIr~Cb{)Q++r2&nk$YB zYHW`wxfid$Jmr^2l7;(Z3%azNrm1UcA4$4;cN=k&i(5`aff|C>P=f>5gZJeX37e zX=*50@r%+5@1zK~%Gu0!NAI5Y7V7$x91R!=)8G-7X=%C|3#&+yBp+VstX+C5RmraI zF!-wX_?!{O9IbV%`sTcG#%t{9THMF~LOi>Hv=-7T-(@$1F*HwqE)G@BX2eiO2ea5# z%H6n~Pfh11Z_O2t$~`{emel2R|7hA8?H|abt>a7YVbIuTl=