Skip to content

Commit 7c08bd4

Browse files
committed
added backend for gmail permission
1 parent 1e9f70d commit 7c08bd4

29 files changed

+1243
-211
lines changed

backend/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@
3030
<java.version>21</java.version>
3131
<aws.sdk.version>2.25.27</aws.sdk.version>
3232
<jjwt.version>0.11.5</jjwt.version>
33+
<google.apis.version>v1-rev20220404-2.0.0</google.apis.version>
3334
</properties>
3435

3536
<dependencies>
36-
37+
3738
<dependency>
3839
<groupId>org.springframework.boot</groupId>
3940
<artifactId>spring-boot-starter-web</artifactId>
@@ -72,6 +73,12 @@
7273
<artifactId>s3</artifactId>
7374
<version>${aws.sdk.version}</version>
7475
</dependency>
76+
77+
<dependency>
78+
<groupId>com.google.apis</groupId>
79+
<artifactId>google-api-services-gmail</artifactId>
80+
<version>${google.apis.version}</version>
81+
</dependency>
7582

7683
<dependency>
7784
<groupId>org.springframework.boot</groupId>

backend/src/main/java/com/thughari/jobtrackerpro/JobTrackerProApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
import org.springframework.cache.annotation.EnableCaching;
88
import org.springframework.data.web.config.EnableSpringDataWebSupport;
99
import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode;
10+
import org.springframework.scheduling.annotation.EnableAsync;
1011
import org.springframework.scheduling.annotation.EnableScheduling;
1112

1213
@SpringBootApplication
1314
@EnableCaching
1415
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
1516
@EnableScheduling
17+
@EnableAsync
1618
public class JobTrackerProApplication {
1719

1820
public static void main(String[] args) {

backend/src/main/java/com/thughari/jobtrackerpro/config/AsyncConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Primary;
56
import org.springframework.scheduling.annotation.EnableAsync;
67
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
78
import java.util.concurrent.Executor;
@@ -20,4 +21,16 @@ public Executor dashboardExecutor() {
2021
executor.initialize();
2122
return executor;
2223
}
24+
25+
@Primary // This tells Spring: "Use this one for @Async by default"
26+
@Bean(name = "taskExecutor")
27+
public Executor taskExecutor() {
28+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
29+
executor.setCorePoolSize(5);
30+
executor.setMaxPoolSize(15);
31+
executor.setQueueCapacity(100);
32+
executor.setThreadNamePrefix("GmailSync-");
33+
executor.initialize();
34+
return executor;
35+
}
2336
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.thughari.jobtrackerpro.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
6+
import org.springframework.security.crypto.password.PasswordEncoder;
7+
8+
@Configuration
9+
public class PasswordConfig {
10+
11+
@Bean
12+
public PasswordEncoder passwordEncoder() {
13+
// High Performance: BCrypt is standard, but you can tune strength
14+
// if login latency becomes a bottleneck. 10 is the balanced default.
15+
return new BCryptPasswordEncoder();
16+
}
17+
}

backend/src/main/java/com/thughari/jobtrackerpro/config/SecurityConfig.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1515
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
1616
import org.springframework.security.config.http.SessionCreationPolicy;
17-
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
18-
import org.springframework.security.crypto.password.PasswordEncoder;
1917

2018
import org.springframework.security.web.SecurityFilterChain;
2119
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -73,12 +71,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
7371

7472
return http.build();
7573
}
76-
77-
@Bean
78-
public PasswordEncoder passwordEncoder() {
79-
return new BCryptPasswordEncoder();
80-
}
81-
74+
8275
@Bean
8376
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
8477
return config.getAuthenticationManager();
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.thughari.jobtrackerpro.controller;
2+
3+
import com.thughari.jobtrackerpro.service.GmailIntegrationService;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.security.core.context.SecurityContextHolder;
7+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.util.Map;
11+
12+
@RestController
13+
@RequestMapping("/api/integrations")
14+
@Slf4j
15+
public class GmailIntegrationController {
16+
17+
private final GmailIntegrationService gmailAutomationService;
18+
19+
// constructor injection - clean & thread-safe
20+
public GmailIntegrationController(GmailIntegrationService gmailAutomationService) {
21+
this.gmailAutomationService = gmailAutomationService;
22+
}
23+
24+
@PostMapping("/gmail/connect")
25+
public ResponseEntity<String> connectGmail(@RequestBody Map<String, String> body) {
26+
String authCode = body.get("code");
27+
String email = getAuthenticatedEmail();
28+
29+
try {
30+
// Business logic and DB lookup are hidden inside the service
31+
gmailAutomationService.connectAndSetupPush(authCode, email);
32+
return ResponseEntity.ok("Gmail Automation enabled successfully.");
33+
} catch (Exception e) {
34+
log.error("Failed to setup Gmail for user {}: {}", email, e.getMessage());
35+
return ResponseEntity.status(500).body("Failed to setup Gmail: " + e.getMessage());
36+
}
37+
}
38+
39+
@PostMapping("/gmail/sync")
40+
public ResponseEntity<String> syncGmail(OAuth2AuthenticationToken authentication) {
41+
// High Performance: Service uses @Async to return immediately
42+
gmailAutomationService.initiateManualSync(authentication);
43+
return ResponseEntity.ok("Sync started in background. Your dashboard will update shortly.");
44+
}
45+
46+
private String getAuthenticatedEmail() {
47+
return SecurityContextHolder.getContext().getAuthentication().getName().toLowerCase();
48+
}
49+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.thughari.jobtrackerpro.controller;
2+
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import com.thughari.jobtrackerpro.service.GmailWebhookService;
6+
import com.thughari.jobtrackerpro.util.GoogleNotificationDecoder;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.*;
10+
11+
import java.util.Map;
12+
import java.util.concurrent.TimeUnit;
13+
14+
@RestController
15+
@RequestMapping("/api/webhooks")
16+
@Slf4j
17+
public class GmailWebhookController {
18+
19+
private final GmailWebhookService gmailService;
20+
private final GoogleNotificationDecoder decoder;
21+
22+
// HIGH PERFORMANCE: In-memory "Deduplicator"
23+
// Prevents the "Push Avalanche" from hitting the database
24+
private final Cache<String, Boolean> pushDeduplicator = Caffeine.newBuilder()
25+
.expireAfterWrite(15, TimeUnit.SECONDS) // Ignore same user for 15 seconds
26+
.build();
27+
28+
public GmailWebhookController(GmailWebhookService gmailService, GoogleNotificationDecoder decoder) {
29+
this.gmailService = gmailService;
30+
this.decoder = decoder;
31+
}
32+
33+
@PostMapping("/gmail/push")
34+
public ResponseEntity<Void> handleGmailPush(@RequestBody Map<String, Object> body) {
35+
System.out.println("--------------");
36+
System.out.println(body);
37+
System.out.println("--------------");
38+
String email = decoder.extractEmail(body);
39+
System.out.println("--------------");
40+
System.out.println(email);
41+
System.out.println("--------------");
42+
43+
if (email != null) {
44+
// Check if we already handled a push for this user in the last 5 seconds
45+
if (pushDeduplicator.getIfPresent(email) == null) {
46+
pushDeduplicator.put(email, true);
47+
log.info("Processing valid Gmail Push for: {}", email);
48+
49+
// Hand off to @Async service
50+
gmailService.processHistorySync(email);
51+
} else {
52+
log.debug("Discarding redundant push notification for: {}", email);
53+
}
54+
}
55+
56+
return ResponseEntity.ok().build();
57+
}
58+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.thughari.jobtrackerpro.dto;
2+
3+
public record EmailBatchItem(String from, String subject, String body) {}

backend/src/main/java/com/thughari/jobtrackerpro/dto/UserProfileResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ public class UserProfileResponse {
1111
private String imageUrl;
1212
private String provider;
1313
private boolean hasPassword;
14+
private boolean gmailConnected;
15+
private boolean gmailSyncInProgress;
1416
}

backend/src/main/java/com/thughari/jobtrackerpro/entity/Job.java

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,42 @@
1010
import jakarta.persistence.GenerationType;
1111
import jakarta.persistence.Id;
1212
import jakarta.persistence.Table;
13+
import jakarta.persistence.Index;
14+
1315

1416
@Data
1517
@Entity
16-
@Table(name = "jobs")
18+
@Table(name = "jobs", indexes = {
19+
@Index(name = "idx_jobs_user_email_updated_at", columnList = "userEmail, updatedAt DESC"),
20+
@Index(name = "idx_jobs_user_status", columnList = "userEmail, status")
21+
})
1722
public class Job {
18-
@Id
19-
@GeneratedValue(strategy = GenerationType.UUID)
20-
@Column(columnDefinition = "uuid") //@Column(columnDefinition = "VARCHAR(36)") -- for mySQL
21-
private UUID id;
23+
@Id
24+
@GeneratedValue(strategy = GenerationType.UUID)
25+
@Column(columnDefinition = "uuid") //@Column(columnDefinition = "VARCHAR(36)") -- for mySQL
26+
private UUID id;
27+
28+
@Column(nullable = false)
29+
private String userEmail;
2230

23-
private String userEmail;
31+
private String company;
32+
private String role;
33+
private String location;
34+
private LocalDateTime appliedDate;
2435

25-
private String company;
26-
private String role;
27-
private String location;
28-
private LocalDateTime appliedDate;
29-
30-
private LocalDateTime updatedAt;
36+
@Column(nullable = false)
37+
private LocalDateTime updatedAt;
3138

32-
private String status;
33-
private Integer stage;
34-
private String stageStatus;
39+
private String status;
40+
private Integer stage;
41+
private String stageStatus;
3542

36-
private Double salaryMin;
37-
private Double salaryMax;
43+
private Double salaryMin;
44+
private Double salaryMax;
3845

39-
@Column(length = 2048)
40-
private String url;
46+
@Column(length = 2048)
47+
private String url;
4148

42-
@Column(length = 4096)
43-
private String notes;
49+
@Column(columnDefinition = "TEXT")
50+
private String notes; //ALTER TABLE jobs ALTER COLUMN notes TYPE TEXT;
4451
}

0 commit comments

Comments
 (0)