Skip to content

Commit 1d0691b

Browse files
committed
feat: move interview count to per-application field
Adds interview_count column to job_applications so each application tracks its own count. Dashboard interviewCount now sums across all applications. Removes the per-user UserInterviewMetrics path and the manual update endpoint/MCP tool (editing is now done via the application form). https://claude.ai/code/session_015kfgrc2oHA881RT3bEYHZ6
1 parent 640bcf3 commit 1d0691b

11 files changed

Lines changed: 31 additions & 103 deletions

File tree

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
package com.jobtracker.controller;
22

33
import com.jobtracker.dto.dashboard.DashboardSummaryResponse;
4-
import com.jobtracker.dto.dashboard.UpdateInterviewCountRequest;
54
import com.jobtracker.service.DashboardService;
6-
import com.jobtracker.service.InterviewMetricsService;
7-
import com.jobtracker.util.SecurityUtils;
85
import io.swagger.v3.oas.annotations.Operation;
96
import io.swagger.v3.oas.annotations.media.Content;
107
import io.swagger.v3.oas.annotations.media.Schema;
118
import io.swagger.v3.oas.annotations.responses.ApiResponse;
129
import io.swagger.v3.oas.annotations.tags.Tag;
13-
import jakarta.validation.Valid;
1410
import org.springframework.http.ResponseEntity;
1511
import org.springframework.web.bind.annotation.GetMapping;
16-
import org.springframework.web.bind.annotation.PatchMapping;
17-
import org.springframework.web.bind.annotation.RequestBody;
1812
import org.springframework.web.bind.annotation.RequestMapping;
1913
import org.springframework.web.bind.annotation.RestController;
2014

@@ -24,15 +18,9 @@
2418
public class DashboardController {
2519

2620
private final DashboardService dashboardService;
27-
private final InterviewMetricsService interviewMetricsService;
28-
private final SecurityUtils securityUtils;
2921

30-
public DashboardController(DashboardService dashboardService,
31-
InterviewMetricsService interviewMetricsService,
32-
SecurityUtils securityUtils) {
22+
public DashboardController(DashboardService dashboardService) {
3323
this.dashboardService = dashboardService;
34-
this.interviewMetricsService = interviewMetricsService;
35-
this.securityUtils = securityUtils;
3624
}
3725

3826
@Operation(
@@ -48,19 +36,4 @@ public DashboardController(DashboardService dashboardService,
4836
public ResponseEntity<DashboardSummaryResponse> getSummary() {
4937
return ResponseEntity.ok(dashboardService.getSummary());
5038
}
51-
52-
@Operation(
53-
summary = "Update interview count",
54-
description = "Manually sets the cumulative interview count for the authenticated user",
55-
responses = {
56-
@ApiResponse(responseCode = "204", description = "Count updated"),
57-
@ApiResponse(responseCode = "400", description = "Invalid count value"),
58-
@ApiResponse(responseCode = "401", description = "Not authenticated")
59-
}
60-
)
61-
@PatchMapping("/interview-count")
62-
public ResponseEntity<Void> updateInterviewCount(@Valid @RequestBody UpdateInterviewCountRequest request) {
63-
interviewMetricsService.setInterviewCount(securityUtils.getCurrentUserId(), request.count());
64-
return ResponseEntity.noContent().build();
65-
}
6639
}

src/main/java/com/jobtracker/dto/application/ApplicationRequest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.JsonFormat;
44
import io.swagger.v3.oas.annotations.media.Schema;
5+
import jakarta.validation.constraints.Min;
56
import jakarta.validation.constraints.NotNull;
67
import jakarta.validation.constraints.PastOrPresent;
78
import jakarta.validation.constraints.Pattern;
@@ -53,5 +54,9 @@ public record ApplicationRequest(
5354
String note,
5455

5556
@Schema(description = "Platform or job board where the vacancy was found", example = "LinkedIn")
56-
String platform
57+
String platform,
58+
59+
@Schema(description = "Number of interviews held for this application", example = "2")
60+
@Min(0)
61+
Integer interviewCount
5762
) {}

src/main/java/com/jobtracker/dto/application/ApplicationResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public record ApplicationResponse(
6060
@Schema(description = "Timestamp when the latest Google Docs resume was generated", example = "2024-06-11T14:45:00")
6161
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
6262
LocalDateTime driveResumeGeneratedAt,
63+
@Schema(description = "Number of interviews held for this application", example = "2")
64+
int interviewCount,
6365
@Schema(description = "Record creation timestamp", example = "2024-06-01T10:00:00")
6466
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
6567
LocalDateTime createdAt,

src/main/java/com/jobtracker/dto/dashboard/UpdateInterviewCountRequest.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/main/java/com/jobtracker/entity/JobApplication.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public class JobApplication {
4343
@Column(name = "interview_scheduled", nullable = false)
4444
private boolean interviewScheduled;
4545

46+
@Column(name = "interview_count", nullable = false)
47+
private int interviewCount = 0;
48+
4649
@Column(name = "next_step_date_time")
4750
private LocalDateTime nextStepDateTime;
4851

@@ -147,6 +150,9 @@ protected void onUpdate() {
147150
public boolean isInterviewScheduled() { return interviewScheduled; }
148151
public void setInterviewScheduled(boolean interviewScheduled) { this.interviewScheduled = interviewScheduled; }
149152

153+
public int getInterviewCount() { return interviewCount; }
154+
public void setInterviewCount(int interviewCount) { this.interviewCount = interviewCount; }
155+
150156
public LocalDateTime getNextStepDateTime() { return nextStepDateTime; }
151157
public void setNextStepDateTime(LocalDateTime nextStepDateTime) { this.nextStepDateTime = nextStepDateTime; }
152158

src/main/java/com/jobtracker/mapper/ApplicationMapper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public ApplicationResponse toResponse(JobApplication app) {
3131
app.getDriveResumeFileName(),
3232
app.getDriveResumeDocumentUrl(),
3333
app.getDriveResumeGeneratedAt(),
34+
app.getInterviewCount(),
3435
app.getCreatedAt(),
3536
app.getUpdatedAt()
3637
);

src/main/java/com/jobtracker/mcp/tools/McpAnalyticsTools.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.jobtracker.mapper.ApplicationMapper;
1010
import com.jobtracker.repository.ApplicationRepository;
1111
import com.jobtracker.service.ApplicationService;
12-
import com.jobtracker.service.InterviewMetricsService;
1312
import com.jobtracker.util.SecurityUtils;
1413
import org.springaicommunity.mcp.annotation.McpTool;
1514
import org.springaicommunity.mcp.annotation.McpTool.McpAnnotations;
@@ -34,39 +33,18 @@ public class McpAnalyticsTools {
3433
private final ApplicationRepository applicationRepository;
3534
private final ApplicationMapper applicationMapper;
3635
private final ApplicationService applicationService;
37-
private final InterviewMetricsService interviewMetricsService;
3836
private final SecurityUtils securityUtils;
3937

4038
public McpAnalyticsTools(ApplicationRepository applicationRepository,
4139
ApplicationMapper applicationMapper,
4240
ApplicationService applicationService,
43-
InterviewMetricsService interviewMetricsService,
4441
SecurityUtils securityUtils) {
4542
this.applicationRepository = applicationRepository;
4643
this.applicationMapper = applicationMapper;
4744
this.applicationService = applicationService;
48-
this.interviewMetricsService = interviewMetricsService;
4945
this.securityUtils = securityUtils;
5046
}
5147

52-
@McpTool(
53-
name = "Update-Interview-Count",
54-
title = "Update Interview Count",
55-
description = "Manually sets the cumulative interview count for the current user. Use this when the automatic counter missed interviews or needs correction.",
56-
annotations = @McpAnnotations(
57-
title = "Update Interview Count",
58-
readOnlyHint = false,
59-
destructiveHint = false,
60-
idempotentHint = true,
61-
openWorldHint = false))
62-
@Transactional
63-
public String updateInterviewCount(
64-
@McpToolParam(required = true, description = "The new total interview count (must be >= 0)") long count) {
65-
UUID userId = securityUtils.getCurrentUserId();
66-
interviewMetricsService.setInterviewCount(userId, count);
67-
return "Interview count updated to " + count;
68-
}
69-
7048
@McpTool(
7149
name = "Get-Analytics",
7250
title = "Get Analytics",

src/main/java/com/jobtracker/repository/ApplicationRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public interface ApplicationRepository extends JpaRepository<JobApplication, UUI
2323

2424
long countByUserIdAndArchivedFalse(UUID userId);
2525

26+
@Query("SELECT COALESCE(SUM(a.interviewCount), 0) FROM JobApplication a WHERE a.user.id = :userId AND a.archived = false")
27+
long sumInterviewCountByUserId(@Param("userId") UUID userId);
28+
2629
long countByUserIdAndInterviewScheduledTrueAndArchivedFalse(UUID userId);
2730

2831
@Query("SELECT COUNT(a) FROM JobApplication a WHERE a.user.id = :userId AND a.status = :status AND a.archived = false")

src/main/java/com/jobtracker/service/ApplicationService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ private void mapRequestToEntity(ApplicationRequest request, JobApplication app)
222222
app.setRecruiterDmReminderEnabled(Boolean.TRUE.equals(request.recruiterDmReminderEnabled()));
223223
app.setNote(normalizeOptionalText(request.note()));
224224
app.setPlatform(request.platform());
225+
if (request.interviewCount() != null) {
226+
app.setInterviewCount(request.interviewCount());
227+
}
225228
}
226229

227230
private void applyStatusChange(JobApplication app, ApplicationStatus newStatus) {

src/main/java/com/jobtracker/service/InterviewMetricsService.java

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22

33
import com.jobtracker.entity.InterviewEvent;
44
import com.jobtracker.entity.JobApplication;
5-
import com.jobtracker.entity.User;
6-
import com.jobtracker.entity.UserInterviewMetrics;
75
import com.jobtracker.entity.enums.ApplicationStatus;
6+
import com.jobtracker.repository.ApplicationRepository;
87
import com.jobtracker.repository.InterviewEventRepository;
9-
import com.jobtracker.repository.UserInterviewMetricsRepository;
10-
import com.jobtracker.repository.UserRepository;
118
import org.springframework.stereotype.Service;
129
import org.springframework.transaction.annotation.Transactional;
1310

@@ -28,16 +25,13 @@ public class InterviewMetricsService {
2825
ApplicationStatus.RH_NEGOCIACAO
2926
);
3027

31-
private final UserInterviewMetricsRepository metricsRepository;
28+
private final ApplicationRepository applicationRepository;
3229
private final InterviewEventRepository eventRepository;
33-
private final UserRepository userRepository;
3430

35-
public InterviewMetricsService(UserInterviewMetricsRepository metricsRepository,
36-
InterviewEventRepository eventRepository,
37-
UserRepository userRepository) {
38-
this.metricsRepository = metricsRepository;
31+
public InterviewMetricsService(ApplicationRepository applicationRepository,
32+
InterviewEventRepository eventRepository) {
33+
this.applicationRepository = applicationRepository;
3934
this.eventRepository = eventRepository;
40-
this.userRepository = userRepository;
4135
}
4236

4337
public boolean isInterviewStatus(String status) {
@@ -67,49 +61,17 @@ public void recordStatusTransition(JobApplication application,
6761
return;
6862
}
6963

70-
User user = application.getUser();
71-
UserInterviewMetrics metrics = findOrCreateMetrics(user);
72-
metrics.setInterviewCount(metrics.getInterviewCount() + 1);
73-
metricsRepository.save(metrics);
74-
7564
InterviewEvent event = new InterviewEvent();
76-
event.setUser(user);
65+
event.setUser(application.getUser());
7766
event.setApplication(application);
7867
event.setOldStatus(oldStatus);
7968
event.setNewStatus(newStatus);
8069
event.setOccurredAt(LocalDateTime.now());
8170
eventRepository.save(event);
8271
}
8372

84-
@Transactional
85-
public void setInterviewCount(UUID userId, long count) {
86-
if (count < 0) throw new IllegalArgumentException("Interview count cannot be negative");
87-
UserInterviewMetrics metrics = metricsRepository.findByUser_Id(userId)
88-
.orElseGet(() -> {
89-
User user = userRepository.getReferenceById(userId);
90-
UserInterviewMetrics created = new UserInterviewMetrics();
91-
created.setUser(user);
92-
created.setInterviewCount(0);
93-
return created;
94-
});
95-
metrics.setInterviewCount(count);
96-
metricsRepository.save(metrics);
97-
}
98-
9973
@Transactional(readOnly = true)
10074
public long getInterviewCount(UUID userId) {
101-
return metricsRepository.findById(userId)
102-
.map(UserInterviewMetrics::getInterviewCount)
103-
.orElseGet(() -> eventRepository.countByUser_Id(userId));
104-
}
105-
106-
private UserInterviewMetrics findOrCreateMetrics(User user) {
107-
return metricsRepository.findByUser_Id(user.getId())
108-
.orElseGet(() -> {
109-
UserInterviewMetrics created = new UserInterviewMetrics();
110-
created.setUser(user);
111-
created.setInterviewCount(0);
112-
return created;
113-
});
75+
return applicationRepository.sumInterviewCountByUserId(userId);
11476
}
11577
}

0 commit comments

Comments
 (0)