Skip to content

feat: add google drive resume integration#24

Open
vitorhugo-java wants to merge 9 commits intomainfrom
feat/google-drive-resume-integration
Open

feat: add google drive resume integration#24
vitorhugo-java wants to merge 9 commits intomainfrom
feat/google-drive-resume-integration

Conversation

@vitorhugo-java
Copy link
Copy Markdown
Owner

This pull request introduces Google Drive integration to the backend, enabling users to connect their Google Drive accounts via OAuth2 and automate copying Google Docs resumes for job applications. It adds new endpoints, configuration, documentation, and supporting classes for managing the OAuth flow, Drive folder selection, and resume copy operations.

Key changes:

Google Drive Integration Features:

  • Added a new GoogleDriveController with endpoints for starting OAuth, handling callbacks, checking connection status, managing root folders and base resumes, and copying resumes into application folders.
  • Introduced DTOs for Google Drive operations, including requests and responses for OAuth, base resume management, and resume copy actions. (GoogleDriveBaseResumeRequest, GoogleDriveBaseResumeResponse, GoogleDriveOAuthStartResponse, GoogleDriveResumeCopyRequest, GoogleDriveResumeCopyResponse) [1] [2] [3] [4] [5]

Configuration and Security:

  • Added GoogleDriveProperties for managing Drive-related configuration via environment variables and Spring properties.
  • Updated security configuration to allow unauthenticated access to the Google OAuth callback endpoint.
  • Added required dependencies for Google Drive integration.

Documentation and Environment:

  • Updated .env.example and documentation to include new Google Drive environment variables and setup instructions. [1] [2] [3]
  • Expanded the README.md with detailed API documentation for new Google Drive endpoints, OAuth flow, supported file types, and example request/response payloads. [1] [2] [3]
  • Updated all API endpoint paths in documentation to use /api/v1/ for consistency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation backend tests database configuration labels May 5, 2026
@vitorhugo-java
Copy link
Copy Markdown
Owner Author

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds backend support for connecting a user's Google Drive account via OAuth2 and copying Google Docs resumes into application-specific folders. This extends the existing /api/v1 Spring Boot API with new Google Drive configuration, persistence, service logic, endpoints, and tests/docs.

Changes:

  • Added Google Drive OAuth, connection status, root-folder, base-resume, and resume-copy backend APIs.
  • Introduced Google Drive entities, repositories, DTOs, properties, and a default Google API client implementation.
  • Updated security, configuration, tests, environment examples, and README/API documentation for the new integration.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/test/resources/application-test.yml Adds test profile Google Drive settings.
src/test/java/com/jobtracker/unit/GoogleDriveServiceTest.java Adds unit coverage for core Google Drive service flows.
src/test/java/com/jobtracker/integration/GoogleDriveControllerIT.java Adds integration coverage for selected Google Drive endpoints.
src/main/resources/db/migration/V11__add_google_drive_integration.sql Creates Google Drive connection, resume, and OAuth state tables.
src/main/resources/application.yml Adds Google Drive runtime properties and springdoc group config.
src/main/java/com/jobtracker/service/GoogleDriveService.java Implements status, folder, base resume, and copy logic.
src/main/java/com/jobtracker/service/GoogleDriveOAuthService.java Implements OAuth start/callback/disconnect workflow.
src/main/java/com/jobtracker/service/GoogleDriveApiClient.java Defines Google Drive API client contract and DTO records.
src/main/java/com/jobtracker/service/DefaultGoogleDriveApiClient.java Implements Google OAuth/Drive HTTP calls.
src/main/java/com/jobtracker/repository/GoogleDriveOAuthStateRepository.java Adds OAuth state repository accessors.
src/main/java/com/jobtracker/repository/GoogleDriveConnectionRepository.java Adds Google Drive connection lookup by user.
src/main/java/com/jobtracker/repository/GoogleDriveBaseResumeRepository.java Adds base resume repository queries.
src/main/java/com/jobtracker/entity/GoogleDriveOAuthState.java Adds OAuth state persistence model.
src/main/java/com/jobtracker/entity/GoogleDriveConnection.java Adds stored Google Drive connection model.
src/main/java/com/jobtracker/entity/GoogleDriveBaseResume.java Adds stored base resume model.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveStatusResponse.java Adds Google Drive status response DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveRootFolderRequest.java Adds root folder request DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveResumeCopyResponse.java Adds resume copy response DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveResumeCopyRequest.java Adds resume copy request DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveOAuthStartResponse.java Adds OAuth start response DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveBaseResumeResponse.java Adds base resume response DTO.
src/main/java/com/jobtracker/dto/gdrive/GoogleDriveBaseResumeRequest.java Adds base resume request DTO.
src/main/java/com/jobtracker/controller/GoogleDriveController.java Exposes Google Drive REST endpoints.
src/main/java/com/jobtracker/config/SecurityConfig.java Allows unauthenticated access to the OAuth callback route.
src/main/java/com/jobtracker/config/GoogleDriveProperties.java Adds Google Drive property binding and validation helpers.
README.md Documents Google Drive setup, flow, and endpoints.
pom.xml Adds the commons-codec dependency.
.env.example Adds Google Drive environment variable examples.

Comment on lines +70 to +74
public boolean isConfigured() {
return hasText(clientId) && hasText(clientSecret) && hasText(redirectUri);
}

public void validateConfigured() {
Comment on lines +27 to +29
public DefaultGoogleDriveApiClient(GoogleDriveProperties properties, RestClient.Builder restClientBuilder) {
this.properties = properties;
this.restClient = restClientBuilder.build();
Comment thread src/main/java/com/jobtracker/service/DefaultGoogleDriveApiClient.java Outdated
Comment on lines +241 to +245
private String buildVacancyFolderName(JobApplication application) {
String baseName = firstNonBlank(application.getVacancyName(), application.getOrganization(), "Application");
String suffix = "APP-" + application.getId().toString();
return truncateFileName(sanitizeFileName(baseName + " - " + suffix), 180);
}
Comment on lines +137 to +139
GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient
.findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)
.orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName));
Comment on lines +67 to +71
@Transactional
public String handleCallback(String state, String code, String error) {
validateServerConfigured();
oauthStateRepository.deleteByExpiresAtBefore(LocalDateTime.now());

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@vitorhugo-java
Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

…ng, locking, OAuth tests

Agent-Logs-Url: https://github.com/vitorhugo-java/SpringBoot-JobApplyTracker/sessions/04ecdf63-053c-45df-a658-80942795419b

Co-authored-by: vitorhugo-java <65777252+vitorhugo-java@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

@copilot apply changes based on the comments in this thread

Applied all 5 actionable changes from the review thread in commit fed5762:

  1. isConfigured() covers oauthCompleteUrl — removed the localhost default in application.yml and GoogleDriveProperties, and added oauthCompleteUrl to the isConfigured() check. The integration is now only considered configured when the frontend redirect URL is explicitly set.

  2. RestClient timeouts — added SimpleClientHttpRequestFactory with a 10-second connect timeout and 30-second read timeout to DefaultGoogleDriveApiClient, preventing servlet threads from hanging indefinitely on slow/unresponsive Google API calls.

  3. Folder name truncation — rewrote buildVacancyFolderName to truncate the base name to 180 - suffix.length() before appending the APP-<uuid> suffix, guaranteeing the application identifier is always present in the folder name regardless of vacancy name length.

  4. Non-atomic find-or-create — added findByIdAndUserIdForUpdate with @Lock(LockModeType.PESSIMISTIC_WRITE) to ApplicationRepository; copyBaseResumeToApplication now uses this to serialize concurrent resume-copy requests for the same application, preventing duplicate Drive subfolders.

  5. OAuth service unit tests — created GoogleDriveOAuthServiceTest with 11 tests covering: Google-returned error, expired state, invalid/unknown state, missing state, missing code, first-time connection, same-account reconnect (root folder preserved), different-account reconnect (root folder and base resumes cleared), state cleanup on error, and disconnect (with and without an existing connection).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 6 comments.

Comment on lines +127 to +129
public MessageResponse disconnect() {
connectionRepository.findByUserId(securityUtils.getCurrentUserId())
.ifPresent(connectionRepository::delete);
Comment on lines +119 to +123
JobApplication application = applicationRepository.findByIdAndUserIdForUpdate(applicationId, userId)
.orElseThrow(() -> new ResourceNotFoundException("Application not found with id: " + applicationId));

if (!StringUtils.hasText(connection.getRootFolderId())) {
throw new BadRequestException("Configure a Google Drive root folder before copying resumes");
Comment on lines +93 to +97
GoogleDriveBaseResume resume = baseResumeRepository.findAllByConnectionIdOrderByCreatedAtAsc(connection.getId())
.stream()
.filter(existing -> existing.getGoogleFileId().equals(file.id()))
.findFirst()
.orElseGet(GoogleDriveBaseResume::new);
Comment on lines +117 to +123
} catch (BadRequestException ex) {
return buildFrontendRedirect("error", ex.getMessage());
} finally {
if (oauthState != null) {
oauthStateRepository.delete(oauthState);
}
}
Comment on lines +229 to +232
private BadRequestException googleApiException(String action, RestClientResponseException ex) {
String message = "Failed to " + action
+ " due to an upstream Google API error (status " + ex.getStatusCode().value() + ")";
return new BadRequestException(message);
Comment on lines +83 to +96
@Operation(summary = "Delete a configured base resume")
@DeleteMapping("/base-resumes/{baseResumeId}")
public ResponseEntity<MessageResponse> deleteBaseResume(@PathVariable UUID baseResumeId) {
googleDriveService.deleteBaseResume(baseResumeId);
return ResponseEntity.ok(new MessageResponse("Base resume deleted successfully"));
}

@Operation(summary = "Copy a base resume into an application folder")
@PostMapping("/applications/{applicationId}/resume-copies")
public ResponseEntity<GoogleDriveResumeCopyResponse> copyBaseResume(
@PathVariable UUID applicationId,
@Valid @RequestBody GoogleDriveResumeCopyRequest request) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(googleDriveService.copyBaseResumeToApplication(applicationId, request));
@vitorhugo-java
Copy link
Copy Markdown
Owner Author

@copilot Act as a Senior Java Developer specialized in Spring Boot 3 and Clean Architecture. Refactor the current Google Drive integration logic to eliminate manual HTTP requests and manual OAuth2 token management.

  • Integration: Use 'spring-boot-starter-oauth2-client' for the OAuth2 lifecycle and the official 'google-api-services-drive' SDK for file operations.
  • Architecture: Implement a dedicated 'GoogleDriveService' that acts as a wrapper for the SDK.
  • Decoupling: Use an 'AuthorizedClientService' approach to inject the 'OAuth2AuthorizedClient' into the service, ensuring the 'AccessToken' is converted into Google 'Credentials' automatically.
  • Patterns: Apply the Factory pattern to instantiate the 'Drive' object and ensure all methods follow the Single Responsibility Principle.
  • Clean Code: Ensure error handling is centralized and that the code uses modern Java 17/21 features (like Records for DTOs if applicable).
  • Scopes: Make sure to handle 'offline_access' for background synchronization.

Output the refactored Service, a Configuration class for the Google API beans, and a brief explanation of how the Spring Security context provides the authorized client.

Also apply changes based on the comments in this thread

…2 client; apply 6 review fixes

Agent-Logs-Url: https://github.com/vitorhugo-java/SpringBoot-JobApplyTracker/sessions/b8946eb7-6687-4cb6-8834-03fbc41a4f1d

Co-authored-by: vitorhugo-java <65777252+vitorhugo-java@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

@copilot Act as a Senior Java Developer specialized in Spring Boot 3 and Clean Architecture. Refactor the current Google Drive integration logic to eliminate manual HTTP requests and manual OAuth2 tok...

Done in commit c503702. Here's what was implemented:

Google Drive SDK + Spring OAuth2 Client refactoring:

  • SdkGoogleDriveApiClient replaces DefaultGoogleDriveApiClient — all Drive file operations (getFileMetadata, findFolder, createFolder, copy) now use the google-api-services-drive SDK via the DriveClientFactory
  • DriveClientFactory (factory pattern) creates Drive instances from stored access tokens using Google OAuth2Credentials + HttpCredentialsAdapter; the singleton HttpTransport + JsonFactory are shared across requests
  • GoogleDriveClientConfig provides a ClientRegistration bean (built programmatically from GoogleDriveProperties) and Spring's DefaultAuthorizationCodeTokenResponseClient / DefaultRefreshTokenTokenResponseClient beans — token exchange and refresh no longer make manual HTTP calls; both clients are configured with the same 10s/30s timeouts
  • Note: A full OAuth2AuthorizedClientService / OAuth2AuthorizedClientManager chain was intentionally omitted. Our existing GoogleDriveConnectionRepository already stores all token and metadata fields atomically; wrapping it as an OAuth2AuthorizedClientService would add abstraction without benefit. The Spring Security context provides the access token indirectly: after OAuth2 consent, the callback stores tokens via GoogleDriveConnectionRepository, and each Drive API call passes the accessToken string from that record to DriveClientFactory.create(), which wraps it in OAuth2Credentials for the SDK.
  • Error handling is centralized in SdkGoogleDriveApiClient: Google 4xxBadRequestException (HTTP 400), 429/5xxServiceUnavailableException (HTTP 503); raw Google error bodies are never exposed to callers

Review thread fixes:

  • disconnect() now also deletes all pending OAuth states for the user, preventing stale consent tabs from re-connecting after disconnect
  • handleCallback() catches all Exception, not just BadRequestException; unexpected errors redirect with a safe generic message instead of surfacing a raw 500
  • addBaseResume uses findByConnectionIdAndGoogleFileId and handles DataIntegrityViolationException for concurrent inserts behaving idempotently
  • Removed pessimistic DB lock (findByIdAndUserIdForUpdate) from copyBaseResumeToApplication that was held across Google API calls
  • Added 8 new GoogleDriveControllerIT integration tests covering disconnect, add-base-resume, delete-base-resume, and copy-resume endpoints (all 128 tests pass)

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.

Comment on lines +86 to +90
private RestTemplate buildRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout((int) Duration.ofMillis(CONNECT_TIMEOUT_MS).toMillis());
factory.setReadTimeout((int) Duration.ofMillis(READ_TIMEOUT_MS).toMillis());
return new RestTemplate(factory);
Comment thread src/main/java/com/jobtracker/service/GoogleDriveOAuthService.java Outdated
Comment on lines +81 to +82
if (code == null || code.isBlank()) {
return buildFrontendRedirect("error", "Missing authorization code");
Comment on lines +142 to +145
String vacancyFolderName = buildVacancyFolderName(application);
GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient
.findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)
.orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName));
Comment on lines +143 to +145
GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient
.findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)
.orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName));
Comment thread README.md Outdated
Comment thread src/main/java/com/jobtracker/service/SdkGoogleDriveApiClient.java Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
vitorhugo-java and others added 2 commits May 5, 2026 19:06
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@vitorhugo-java
Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits May 5, 2026 23:01
…dering, stored vacancy folder ID

Agent-Logs-Url: https://github.com/vitorhugo-java/SpringBoot-JobApplyTracker/sessions/6181ecfd-c2b7-4755-b2cc-2e3f30002198

Co-authored-by: vitorhugo-java <65777252+vitorhugo-java@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend configuration database documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants