feat: add google drive resume integration#24
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
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. |
| public boolean isConfigured() { | ||
| return hasText(clientId) && hasText(clientSecret) && hasText(redirectUri); | ||
| } | ||
|
|
||
| public void validateConfigured() { |
| public DefaultGoogleDriveApiClient(GoogleDriveProperties properties, RestClient.Builder restClientBuilder) { | ||
| this.properties = properties; | ||
| this.restClient = restClientBuilder.build(); |
| 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); | ||
| } |
| GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient | ||
| .findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName) | ||
| .orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)); |
| @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>
|
@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>
Applied all 5 actionable changes from the review thread in commit
|
| public MessageResponse disconnect() { | ||
| connectionRepository.findByUserId(securityUtils.getCurrentUserId()) | ||
| .ifPresent(connectionRepository::delete); |
| 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"); |
| GoogleDriveBaseResume resume = baseResumeRepository.findAllByConnectionIdOrderByCreatedAtAsc(connection.getId()) | ||
| .stream() | ||
| .filter(existing -> existing.getGoogleFileId().equals(file.id())) | ||
| .findFirst() | ||
| .orElseGet(GoogleDriveBaseResume::new); |
| } catch (BadRequestException ex) { | ||
| return buildFrontendRedirect("error", ex.getMessage()); | ||
| } finally { | ||
| if (oauthState != null) { | ||
| oauthStateRepository.delete(oauthState); | ||
| } | ||
| } |
| 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); |
| @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)); |
|
@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.
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>
Done in commit Google Drive SDK + Spring OAuth2 Client refactoring:
Review thread fixes:
|
| 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); |
| if (code == null || code.isBlank()) { | ||
| return buildFrontendRedirect("error", "Missing authorization code"); |
| String vacancyFolderName = buildVacancyFolderName(application); | ||
| GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient | ||
| .findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName) | ||
| .orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)); |
| GoogleDriveApiClient.DriveFileMetadata vacancyFolder = googleDriveApiClient | ||
| .findFolderByName(connection.getAccessToken(), rootFolder.id(), vacancyFolderName) | ||
| .orElseGet(() -> googleDriveApiClient.createFolder(connection.getAccessToken(), rootFolder.id(), vacancyFolderName)); |
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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
…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>
…ent fallback comment 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>
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:
GoogleDriveControllerwith endpoints for starting OAuth, handling callbacks, checking connection status, managing root folders and base resumes, and copying resumes into application folders.GoogleDriveBaseResumeRequest,GoogleDriveBaseResumeResponse,GoogleDriveOAuthStartResponse,GoogleDriveResumeCopyRequest,GoogleDriveResumeCopyResponse) [1] [2] [3] [4] [5]Configuration and Security:
GoogleDrivePropertiesfor managing Drive-related configuration via environment variables and Spring properties.Documentation and Environment:
.env.exampleand documentation to include new Google Drive environment variables and setup instructions. [1] [2] [3]README.mdwith detailed API documentation for new Google Drive endpoints, OAuth flow, supported file types, and example request/response payloads. [1] [2] [3]/api/v1/for consistency.