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 2619708..66eff4a 100644 Binary files a/src/main/resources/database/BSK.db and b/src/main/resources/database/BSK.db differ