mirror of
https://github.com/tiennm99/BSK-All-in-One-Clinic-Management-System.git
synced 2026-10-11 03:12:53 +00:00
526 lines
22 KiB
Java
526 lines
22 KiB
Java
package BsK.server.service;
|
|
|
|
import com.google.api.client.auth.oauth2.Credential;
|
|
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
|
|
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
|
|
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
|
|
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
|
|
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
|
|
import com.google.api.client.http.FileContent;
|
|
import com.google.api.client.json.JsonFactory;
|
|
import com.google.api.client.json.gson.GsonFactory;
|
|
import com.google.api.client.util.store.FileDataStoreFactory;
|
|
import com.google.api.services.drive.Drive;
|
|
import com.google.api.services.drive.DriveScopes;
|
|
import com.google.api.services.drive.model.File;
|
|
import com.google.api.services.drive.model.FileList;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.security.GeneralSecurityException;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
|
|
@Slf4j
|
|
public class GoogleDriveServiceOAuth {
|
|
private static final String APPLICATION_NAME = "BSK Clinic Management System";
|
|
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
|
|
private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE);
|
|
|
|
// Path to OAuth2 client credentials file
|
|
private static final String OAUTH_CREDENTIALS_PATH = "google-oauth-credentials.json";
|
|
// Directory to store authorization tokens
|
|
private static final String TOKENS_DIRECTORY_PATH = "tokens";
|
|
|
|
private Drive driveService;
|
|
private String rootFolderId; // BSK Clinic root folder
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
private Drive buildDriveService() throws GeneralSecurityException, IOException {
|
|
// Load client secrets
|
|
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
|
|
JSON_FACTORY,
|
|
new InputStreamReader(new FileInputStream(OAUTH_CREDENTIALS_PATH))
|
|
);
|
|
|
|
// Build flow and trigger user authorization request
|
|
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
|
|
GoogleNetHttpTransport.newTrustedTransport(),
|
|
JSON_FACTORY,
|
|
clientSecrets,
|
|
SCOPES)
|
|
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
|
|
.setAccessType("offline")
|
|
.build();
|
|
|
|
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
|
|
Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
|
|
|
|
// Build a new authorized API client service
|
|
return new Drive.Builder(
|
|
GoogleNetHttpTransport.newTrustedTransport(),
|
|
JSON_FACTORY,
|
|
credential)
|
|
.setApplicationName(APPLICATION_NAME)
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Get or create the root folder for BSK Clinic
|
|
*/
|
|
private String getOrCreateRootFolder() throws IOException {
|
|
String folderName = this.rootFolderName;
|
|
|
|
// 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<File> 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<String> 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<String> 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<File> files = result.getFiles();
|
|
if (files != null && !files.isEmpty()) {
|
|
return Optional.of(files.get(0).getId());
|
|
}
|
|
|
|
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<String> 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<String> 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<File> 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();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public void runConnectionTest() throws IOException {
|
|
log.info("Starting Google Drive OAuth connection test...");
|
|
|
|
// Create test folder
|
|
String testFolderId = createPatientFolder("TEST002", "OAuth Test Patient");
|
|
|
|
// Create a test text file
|
|
java.io.File testFile = new java.io.File("test-oauth-connection.txt");
|
|
try {
|
|
java.io.FileWriter writer = new java.io.FileWriter(testFile);
|
|
writer.write("BSK Clinic - Google Drive OAuth Connection Test\n");
|
|
writer.write("Timestamp: " + java.time.LocalDateTime.now() + "\n");
|
|
writer.write("This file verifies that the Google Drive OAuth integration is working correctly.\n");
|
|
writer.write("Authentication method: OAuth2 with user consent\n");
|
|
writer.close();
|
|
|
|
// Upload the test file
|
|
String fileId = uploadFile(testFolderId, testFile, "oauth-connection-test.txt");
|
|
|
|
// Get sharing URL
|
|
String folderUrl = getFolderSharingUrl(testFolderId);
|
|
|
|
log.info("✅ Google Drive OAuth connection test successful!");
|
|
log.info("📁 Test folder URL: {}", folderUrl);
|
|
log.info("📄 Test file ID: {}", fileId);
|
|
|
|
// Clean up test file
|
|
testFile.delete();
|
|
|
|
} catch (Exception e) {
|
|
log.error("❌ Google Drive OAuth connection test failed", e);
|
|
if (testFile.exists()) {
|
|
testFile.delete();
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private String sanitizeFileName(String fileName) {
|
|
// Remove or replace invalid characters for file names
|
|
return fileName.replaceAll("[^a-zA-Z0-9._-]", "_");
|
|
}
|
|
|
|
private String determineMimeType(String fileName) {
|
|
String lowerFileName = fileName.toLowerCase();
|
|
if (lowerFileName.endsWith(".pdf")) {
|
|
return "application/pdf";
|
|
} else if (lowerFileName.endsWith(".jpg") || lowerFileName.endsWith(".jpeg")) {
|
|
return "image/jpeg";
|
|
} else if (lowerFileName.endsWith(".png")) {
|
|
return "image/png";
|
|
} else if (lowerFileName.endsWith(".txt")) {
|
|
return "text/plain";
|
|
} else if (lowerFileName.endsWith(".doc")) {
|
|
return "application/msword";
|
|
} else if (lowerFileName.endsWith(".docx")) {
|
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
}
|
|
return "application/octet-stream"; // Default binary
|
|
}
|
|
|
|
public String getRootFolderId() {
|
|
return rootFolderId;
|
|
}
|
|
|
|
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);
|
|
|
|
// --- OVERRIDE LOGIC START ---
|
|
// 1. Find any existing files with the same name in the target folder
|
|
findFilesByName(folderId, fileName).forEach(file -> {
|
|
try {
|
|
log.info("Deleting existing file '{}' (ID: {}) to override.", file.getName(), file.getId());
|
|
driveService.files().delete(file.getId()).execute();
|
|
} catch (IOException e) {
|
|
log.error("Failed to delete existing file (ID: {})", file.getId(), e);
|
|
// We wrap in a RuntimeException because the lambda can't throw a checked IOException
|
|
throw new RuntimeException("Failed to delete existing file for override", e);
|
|
}
|
|
});
|
|
// --- OVERRIDE LOGIC END ---
|
|
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* Helper method to find all files with a specific name inside a parent folder.
|
|
* @param parentFolderId The ID of the folder to search within.
|
|
* @param fileName The exact name of the files to find.
|
|
* @return A list of File objects matching the name.
|
|
* @throws IOException if the Google Drive API call fails.
|
|
*/
|
|
private List<File> findFilesByName(String parentFolderId, String fileName) throws IOException {
|
|
String query = String.format("name = '%s' and '%s' in parents and trashed = false",
|
|
fileName.replace("'", "\\'"), // Escape single quotes in filename
|
|
parentFolderId);
|
|
|
|
FileList result = driveService.files().list()
|
|
.setQ(query)
|
|
.setSpaces("drive")
|
|
.setFields("nextPageToken, files(id, name)")
|
|
.execute();
|
|
|
|
return result.getFiles();
|
|
}
|
|
|
|
/**
|
|
* Finds a folder by name within a specific parent folder, or creates it if it doesn't exist.
|
|
* @param parentFolderId The ID of the parent folder.
|
|
* @param folderName The name of the folder to find or create.
|
|
* @return The ID of the found or newly created folder.
|
|
* @throws IOException if a Google Drive API error occurs.
|
|
*/
|
|
private String findOrCreateFolder(String parentFolderId, String folderName) throws IOException {
|
|
// Search for existing folder
|
|
String query = String.format("name = '%s' and mimeType='application/vnd.google-apps.folder' and '%s' in parents and trashed=false",
|
|
folderName.replace("'", "\\'"),
|
|
parentFolderId);
|
|
|
|
FileList result = driveService.files().list()
|
|
.setQ(query)
|
|
.setSpaces("drive")
|
|
.setFields("files(id)")
|
|
.execute();
|
|
|
|
if (!result.getFiles().isEmpty()) {
|
|
String existingFolderId = result.getFiles().get(0).getId();
|
|
log.info("Found existing folder '{}' (ID: {}) inside parent {}", folderName, existingFolderId, parentFolderId);
|
|
return existingFolderId;
|
|
}
|
|
|
|
// Create folder if it doesn't exist
|
|
log.info("Creating new folder '{}' under parent {}", folderName, parentFolderId);
|
|
return createFolderUnderParent(parentFolderId, folderName);
|
|
}
|
|
|
|
|
|
// Add this primary backup method inside the GoogleDriveServiceOAuth class
|
|
/**
|
|
* Backs up the specified database file to a dedicated backup folder in Google Drive.
|
|
* The backup filename will be timestamped.
|
|
* @param dbFile The local database file to upload.
|
|
* @return The ID of the uploaded file in Google Drive.
|
|
* @throws IOException if the upload fails.
|
|
*/
|
|
public String backupDatabaseFile(java.io.File dbFile) throws IOException {
|
|
// 1. Define the dedicated, private backup folder name
|
|
String backupFolderName = "_Database_Backups"; // Using underscore to sort it to the top
|
|
|
|
// 2. Get or create the backup folder under the main application root folder
|
|
String backupFolderId = findOrCreateFolder(this.rootFolderId, backupFolderName);
|
|
log.info("Using database backup folder: {} (ID: {})", backupFolderName, backupFolderId);
|
|
|
|
// 3. Create a unique, timestamped filename for the backup
|
|
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new java.util.Date());
|
|
String backupFileName = "BSK_Backup_" + timestamp + ".db";
|
|
|
|
// 4. Upload the file
|
|
log.info("Uploading database backup as '{}' to Google Drive...", backupFileName);
|
|
String fileId = uploadFileToFolder(backupFolderId, dbFile, backupFileName);
|
|
log.info("✅ Successfully uploaded database backup. File ID: {}", fileId);
|
|
|
|
return fileId;
|
|
}
|
|
} |