Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/main/java/com/jobtracker/dto/auth/RegisterRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jobtracker.dto.auth;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
Expand All @@ -25,5 +26,9 @@ public record RegisterRequest(

@Schema(description = "Confirm password (must match password)", example = "secureP@ss1")
@NotBlank(message = "Confirm password is required")
String confirmPassword
String confirmPassword,

@Schema(description = "User must accept the Privacy Policy to register", example = "true")
@AssertTrue(message = "You must accept the Privacy Policy to create an account")
boolean acceptedPrivacyPolicy
) {}
4 changes: 3 additions & 1 deletion src/main/java/com/jobtracker/dto/auth/UserResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ public record UserResponse(
@Schema(description = "Granted application roles", example = "[\"USER\", \"BETA\"]")
Set<String> roles,
@Schema(description = "Whether the user can access Google integration features", example = "true")
boolean canUseGoogleIntegration
boolean canUseGoogleIntegration,
@Schema(description = "Whether the user has accepted the Privacy Policy", example = "true")
boolean privacyPolicyAccepted
) {}
74 changes: 68 additions & 6 deletions src/main/java/com/jobtracker/entity/ToolExecutionMetric.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.jobtracker.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.UuidGenerator;

import java.time.LocalDateTime;
Expand All @@ -13,11 +12,6 @@
@Index(name = "idx_tool_metrics_created_at", columnList = "created_at"),
@Index(name = "idx_tool_metrics_expensive", columnList = "expensive")
})
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ToolExecutionMetric {

@Id
Expand Down Expand Up @@ -58,4 +52,72 @@ protected void onCreate() {
createdAt = LocalDateTime.now();
}
}

public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }

public String getToolName() { return toolName; }
public void setToolName(String toolName) { this.toolName = toolName; }

public long getExecutionTimeMs() { return executionTimeMs; }
public void setExecutionTimeMs(long executionTimeMs) { this.executionTimeMs = executionTimeMs; }

public int getRequestBytes() { return requestBytes; }
public void setRequestBytes(int requestBytes) { this.requestBytes = requestBytes; }

public int getResponseBytes() { return responseBytes; }
public void setResponseBytes(int responseBytes) { this.responseBytes = responseBytes; }

public int getRequestTokens() { return requestTokens; }
public void setRequestTokens(int requestTokens) { this.requestTokens = requestTokens; }

public int getResponseTokens() { return responseTokens; }
public void setResponseTokens(int responseTokens) { this.responseTokens = responseTokens; }

public int getTotalTokens() { return totalTokens; }
public void setTotalTokens(int totalTokens) { this.totalTokens = totalTokens; }

public boolean isExpensive() { return expensive; }
public void setExpensive(boolean expensive) { this.expensive = expensive; }

public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }

public static Builder builder() { return new Builder(); }

public static class Builder {
private String toolName;
private long executionTimeMs;
private int requestBytes;
private int responseBytes;
private int requestTokens;
private int responseTokens;
private int totalTokens;
private boolean expensive;
private LocalDateTime createdAt;

public Builder toolName(String toolName) { this.toolName = toolName; return this; }
public Builder executionTimeMs(long executionTimeMs) { this.executionTimeMs = executionTimeMs; return this; }
public Builder requestBytes(int requestBytes) { this.requestBytes = requestBytes; return this; }
public Builder responseBytes(int responseBytes) { this.responseBytes = responseBytes; return this; }
public Builder requestTokens(int requestTokens) { this.requestTokens = requestTokens; return this; }
public Builder responseTokens(int responseTokens) { this.responseTokens = responseTokens; return this; }
public Builder totalTokens(int totalTokens) { this.totalTokens = totalTokens; return this; }
public Builder expensive(boolean expensive) { this.expensive = expensive; return this; }
public Builder createdAt(LocalDateTime createdAt) { this.createdAt = createdAt; return this; }

public ToolExecutionMetric build() {
ToolExecutionMetric m = new ToolExecutionMetric();
m.toolName = toolName;
m.executionTimeMs = executionTimeMs;
m.requestBytes = requestBytes;
m.responseBytes = responseBytes;
m.requestTokens = requestTokens;
m.responseTokens = responseTokens;
m.totalTokens = totalTokens;
m.expensive = expensive;
m.createdAt = createdAt;
return m;
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/jobtracker/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public class User {
@Column(name = "reminder_time", nullable = false)
private LocalTime reminderTime;

@Column(name = "privacy_policy_accepted", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
private boolean privacyPolicyAccepted;

@Column(name = "privacy_policy_accepted_at")
private LocalDateTime privacyPolicyAcceptedAt;

@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;

Expand Down Expand Up @@ -79,4 +85,10 @@ protected void onUpdate() {

public Set<Role> getRoles() { return roles; }
public void setRoles(Set<Role> roles) { this.roles = roles; }

public boolean isPrivacyPolicyAccepted() { return privacyPolicyAccepted; }
public void setPrivacyPolicyAccepted(boolean privacyPolicyAccepted) { this.privacyPolicyAccepted = privacyPolicyAccepted; }

public LocalDateTime getPrivacyPolicyAcceptedAt() { return privacyPolicyAcceptedAt; }
public void setPrivacyPolicyAcceptedAt(LocalDateTime privacyPolicyAcceptedAt) { this.privacyPolicyAcceptedAt = privacyPolicyAcceptedAt; }
}
3 changes: 2 additions & 1 deletion src/main/java/com/jobtracker/mapper/AuthMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public UserResponse toUserResponse(User user) {
user.getEmail(),
user.getReminderTime(),
roles,
roles.contains(RoleName.BETA.name()));
roles.contains(RoleName.BETA.name()),
user.isPrivacyPolicyAccepted());
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/jobtracker/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public AuthResponse register(RegisterRequest request) {
user.setEmail(request.email());
user.setPasswordHash(passwordEncoder.encode(request.password()));
user.setRoles(Set.of(resolveDefaultUserRole()));
user.setPrivacyPolicyAccepted(true);
user.setPrivacyPolicyAcceptedAt(java.time.LocalDateTime.now());
user = userRepository.save(user);
log.info("event=REGISTRATION_SUCCESS email={} userId={}", user.getEmail(), user.getId());
return buildAuthResponse(user);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE users
ADD COLUMN privacy_policy_accepted TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN privacy_policy_accepted_at DATETIME NULL;
6 changes: 4 additions & 2 deletions src/test/java/com/jobtracker/e2e/ApplicationE2ETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ void setUp() {
"name": "App E2E User",
"email": "appe2e@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""")
.post("/api/v1/auth/register")
Expand Down Expand Up @@ -282,7 +283,8 @@ void getById_shouldReturn404_whenBelongsToAnotherUser() {
.contentType("application/json")
.body("""
{"name": "Other User", "email": "other@example.com",
"password": "pass1234", "confirmPassword": "pass1234"}
"password": "pass1234", "confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true}
""")
.post("/api/v1/auth/register");

Expand Down
18 changes: 12 additions & 6 deletions src/test/java/com/jobtracker/e2e/AuthE2ETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ void fullAuthFlow_register_login_refresh_logout() {
"name": "E2E User",
"email": "e2e@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""")
.when()
Expand Down Expand Up @@ -168,7 +169,8 @@ void register_shouldSetHttpOnlySecureSameSiteCookie() {
"name": "Cookie User",
"email": "cookies@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""")
.when()
Expand All @@ -192,7 +194,8 @@ void register_shouldReturn409_whenEmailDuplicated() {
"name": "Dup User",
"email": "dup@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""";

Expand All @@ -212,7 +215,8 @@ void register_shouldReturn400_whenPasswordsMismatch() {
"name": "Mismatch User",
"email": "mismatch@example.com",
"password": "pass1234",
"confirmPassword": "different"
"confirmPassword": "different",
"acceptedPrivacyPolicy": true
}
""")
.when()
Expand All @@ -231,7 +235,8 @@ void login_shouldReturn401_whenWrongPassword() {
"name": "Wrong Pass",
"email": "wrongpass@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""")
.post("/api/v1/auth/register")
Expand Down Expand Up @@ -265,7 +270,8 @@ void logout_shouldReturnSuccess_andClearCookie() {
"name": "Logout User",
"email": "logout@example.com",
"password": "pass1234",
"confirmPassword": "pass1234"
"confirmPassword": "pass1234",
"acceptedPrivacyPolicy": true
}
""")
.when()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void setUp() throws Exception {
refreshTokenRepository.deleteAll();
userRepository.deleteAll();

RegisterRequest reg = new RegisterRequest("App User", "appuser@example.com", "pass1234", "pass1234");
RegisterRequest reg = new RegisterRequest("App User", "appuser@example.com", "pass1234", "pass1234", true);
MvcResult result = mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(reg)))
Expand Down
Loading
Loading