refactor(platform): target Lombok boilerplate

This commit is contained in:
sechmachine
2026-08-15 11:39:53 +07:00
parent b9b150ff8c
commit 41448903aa
21 changed files with 190 additions and 240 deletions
@@ -2,21 +2,17 @@ package com.lab.labtimesheet.config;
import java.util.Base64;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** Security material used to encrypt integration credentials at rest. */
@ConfigurationProperties("lab.security")
@Getter
@Setter
public class SecurityProperties {
private String masterKey;
public String getMasterKey() {
return masterKey;
}
public void setMasterKey(String masterKey) {
this.masterKey = masterKey;
}
/**
* Decodes and validates the configured AES-256 master key.
*
@@ -6,6 +6,8 @@ import com.lab.labtimesheet.feature.account.model.dto.ActivationForm;
import com.lab.labtimesheet.feature.account.model.dto.CreateAccountForm;
import com.lab.labtimesheet.feature.account.service.AccountService;
import jakarta.validation.Valid;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Controller;
@@ -20,13 +22,10 @@ import org.springframework.web.bind.annotation.PostMapping;
* constraints are mapped to their owning form fields without exposing persistence diagnostics.
*/
@Controller
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class AccountController {
private final AccountService accounts;
AccountController(AccountService accounts) {
this.accounts = accounts;
}
@GetMapping("/admin/accounts/new")
String newAccount(Model model) {
if (!model.containsAttribute("accountForm")) {
@@ -7,24 +7,17 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Hides all non-bootstrap application routes until durable first-Admin initialization completes.
* Only bootstrap pages, health, public assets, and error rendering remain reachable beforehand.
*/
@RequiredArgsConstructor
public class BootstrapAccessFilter extends OncePerRequestFilter {
private final BootstrapService bootstrap;
/**
* Creates the pre-bootstrap access guard.
*
* @param bootstrap durable installation-state service
*/
public BootstrapAccessFilter(BootstrapService bootstrap) {
this.bootstrap = bootstrap;
}
/**
* Returns HTTP 404 for hidden routes before bootstrap so no authentication surface is exposed prematurely.
*
@@ -3,6 +3,8 @@ package com.lab.labtimesheet.feature.account.controller;
import com.lab.labtimesheet.feature.account.model.dto.BootstrapForm;
import com.lab.labtimesheet.feature.account.service.BootstrapService;
import jakarta.validation.Valid;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -16,13 +18,10 @@ import org.springframework.web.server.ResponseStatusException;
/** Renders and processes the one-time first-Admin installation form. */
@Controller
@RequestMapping("/bootstrap")
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class BootstrapController {
private final BootstrapService bootstrap;
BootstrapController(BootstrapService bootstrap) {
this.bootstrap = bootstrap;
}
@GetMapping
String form(Model model) {
requireOpen();
@@ -3,10 +3,14 @@ package com.lab.labtimesheet.feature.account.model.dto;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* Validated activation submission. Password fields remain request-local and are never repopulated by the view.
*/
@Getter
@Setter
public class ActivationForm {
@NotBlank(message = "This activation link is invalid or no longer usable")
private String token;
@@ -34,10 +38,4 @@ public class ActivationForm {
confirmPassword = null;
}
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getConfirmPassword() { return confirmPassword; }
public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; }
}
@@ -3,11 +3,14 @@ package com.lab.labtimesheet.feature.account.model.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* Validated browser input for creating the first administrator.
* The password is deliberately never copied into redirected state or repopulated after validation failure.
*/
@Getter
public class BootstrapForm {
@NotBlank(message = "Email is required")
@Email(message = "Enter a valid email address")
@@ -20,29 +23,15 @@ public class BootstrapForm {
@NotBlank(message = "Password is required")
@Size(min = 12, max = 128, message = "Password must contain 12 through 128 characters")
@Setter
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email == null ? null : email.trim();
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName == null ? null : displayName.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -8,9 +8,12 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
/** Validated, non-secret Admin input for creating an immutable-role account. */
@Getter
public class CreateAccountForm {
@NotBlank(message = "Email is required")
@Email(message = "Enter a valid email address")
@@ -22,15 +25,19 @@ public class CreateAccountForm {
private String displayName;
@NotNull(message = "Role is required")
@Setter
private GlobalRole role;
@Size(max = 64, message = "Student code must contain at most 64 characters")
@Setter
private String studentCode;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Setter
private LocalDate internshipStart;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Setter
private LocalDate internshipEnd;
/**
@@ -67,16 +74,6 @@ public class CreateAccountForm {
return hasText(value) ? value.trim() : null;
}
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email == null ? null : email.trim(); }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName == null ? null : displayName.trim(); }
public GlobalRole getRole() { return role; }
public void setRole(GlobalRole role) { this.role = role; }
public String getStudentCode() { return studentCode; }
public void setStudentCode(String studentCode) { this.studentCode = studentCode; }
public LocalDate getInternshipStart() { return internshipStart; }
public void setInternshipStart(LocalDate internshipStart) { this.internshipStart = internshipStart; }
public LocalDate getInternshipEnd() { return internshipEnd; }
public void setInternshipEnd(LocalDate internshipEnd) { this.internshipEnd = internshipEnd; }
}
@@ -16,6 +16,9 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Persistent global account with immutable role, authentication lifecycle, creator attribution, and optimistic
@@ -23,29 +26,37 @@ import jakarta.persistence.Version;
*/
@Entity
@Table(name = "app_users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Column(nullable = false, length = 320)
@Getter
private String email;
@Column(name = "display_name", nullable = false, length = 120)
@Getter
private String displayName;
@Column(name = "password_hash", length = 255)
@Getter
private String passwordHash;
@Enumerated(EnumType.STRING)
@Column(name = "global_role", nullable = false, length = 16, updatable = false)
@Getter
private GlobalRole globalRole;
@Enumerated(EnumType.STRING)
@Column(name = "account_status", nullable = false, length = 32)
@Getter
private AccountStatus accountStatus;
@Column(name = "activated_at")
@Getter
private Instant activatedAt;
@ManyToOne(fetch = FetchType.LAZY)
@@ -61,10 +72,6 @@ public class AppUser {
@Version
private long version;
/** Required by JPA; domain instances are created through named factories. */
protected AppUser() {
}
private AppUser(String email, String displayName, String passwordHash, GlobalRole globalRole,
AccountStatus accountStatus, Instant activatedAt, AppUser createdBy, Instant now) {
this.email = email;
@@ -124,31 +131,4 @@ public class AppUser {
updatedAt = now;
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public String getDisplayName() {
return displayName;
}
public String getPasswordHash() {
return passwordHash;
}
public GlobalRole getGlobalRole() {
return globalRole;
}
public AccountStatus getAccountStatus() {
return accountStatus;
}
public Instant getActivatedAt() {
return activatedAt;
}
}
@@ -11,6 +11,9 @@ import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Persistent internship lifecycle and inclusive eligibility dates for an Intern account.
@@ -18,6 +21,7 @@ import jakarta.persistence.Version;
*/
@Entity
@Table(name = "intern_profiles")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class InternProfile {
@Id
@Column(name = "user_id")
@@ -33,13 +37,16 @@ public class InternProfile {
private String phone;
@Column(name = "internship_start_date", nullable = false)
@Getter
private LocalDate internshipStartDate;
@Column(name = "internship_end_date", nullable = false)
@Getter
private LocalDate internshipEndDate;
@Enumerated(EnumType.STRING)
@Column(name = "internship_status", nullable = false, length = 24)
@Getter
private InternshipStatus internshipStatus;
@Column(name = "activated_at")
@@ -60,10 +67,6 @@ public class InternProfile {
@Version
private long version;
/** Required by JPA; domain instances are created through {@link #notStarted}. */
protected InternProfile() {
}
private InternProfile(
long userId, String studentCode, LocalDate internshipStartDate, LocalDate internshipEndDate, Instant now) {
this.userId = userId;
@@ -105,15 +108,4 @@ public class InternProfile {
updatedAt = now;
}
public InternshipStatus getInternshipStatus() {
return internshipStatus;
}
public LocalDate getInternshipStartDate() {
return internshipStartDate;
}
public LocalDate getInternshipEndDate() {
return internshipEndDate;
}
}
@@ -10,16 +10,21 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/** Durable singleton installation state used to serialize and remember first-Admin bootstrap. */
@Entity
@Table(name = "system_state")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SystemState {
@Id
@Column(name = "singleton_id")
private short singletonId;
@Column(nullable = false)
@Getter
private boolean initialized;
@Column(name = "initialized_at")
@@ -38,14 +43,6 @@ public class SystemState {
@Version
private long version;
/** Required by JPA; Flyway creates the singleton row. */
protected SystemState() {
}
public boolean isInitialized() {
return initialized;
}
/**
* Marks the installation initialized and retains the first Admin attribution.
*
@@ -12,6 +12,9 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Persistent one-time user-action token state. Only a defensive copy of the SHA-256 token hash is stored; raw
@@ -19,28 +22,35 @@ import jakarta.persistence.Table;
*/
@Entity
@Table(name = "user_action_tokens")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserActionToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Column(name = "user_id", nullable = false)
@Getter
private Long userId;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 24)
@Getter
private TokenPurpose purpose;
@Column(name = "token_hash", nullable = false, columnDefinition = "bytea")
private byte[] tokenHash;
@Column(name = "expires_at", nullable = false)
@Getter
private Instant expiresAt;
@Column(name = "used_at")
@Getter
private Instant usedAt;
@Column(name = "invalidated_at")
@Getter
private Instant invalidatedAt;
@Column(name = "issued_by_user_id")
@@ -49,10 +59,6 @@ public class UserActionToken {
@Column(name = "created_at", nullable = false)
private Instant createdAt;
/** Required by JPA; domain instances are created through named factories. */
protected UserActionToken() {
}
private UserActionToken(long userId, byte[] tokenHash, Instant expiresAt, long issuedByUserId, Instant now) {
this.userId = userId;
this.purpose = TokenPurpose.ACTIVATION;
@@ -115,18 +121,6 @@ public class UserActionToken {
}
}
public Long getId() {
return id;
}
public Long getUserId() {
return userId;
}
public TokenPurpose getPurpose() {
return purpose;
}
/**
* Returns a defensive copy of the persisted token hash.
*
@@ -136,15 +130,4 @@ public class UserActionToken {
return Arrays.copyOf(tokenHash, tokenHash.length);
}
public Instant getExpiresAt() {
return expiresAt;
}
public Instant getUsedAt() {
return usedAt;
}
public Instant getInvalidatedAt() {
return invalidatedAt;
}
}
@@ -7,6 +7,8 @@ import com.lab.labtimesheet.feature.account.model.entity.AppUser;
import com.lab.labtimesheet.feature.account.model.entity.SystemState;
import com.lab.labtimesheet.feature.account.repository.AppUserRepository;
import com.lab.labtimesheet.feature.account.repository.SystemStateRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -16,20 +18,13 @@ import org.springframework.transaction.annotation.Transactional;
* Successful creation persists the first active Admin and initialization marker atomically.
*/
@Service
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class BootstrapService {
private final SystemStateRepository systemStates;
private final AppUserRepository users;
private final PasswordEncoder passwords;
private final Clock clock;
BootstrapService(SystemStateRepository systemStates, AppUserRepository users, PasswordEncoder passwords,
Clock clock) {
this.systemStates = systemStates;
this.users = users;
this.passwords = passwords;
this.clock = clock;
}
/**
* Creates the first active Admin exactly once.
*
@@ -2,6 +2,8 @@ package com.lab.labtimesheet.feature.account.service;
import com.lab.labtimesheet.feature.account.model.AccountStatus;
import com.lab.labtimesheet.feature.account.repository.AppUserRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -11,13 +13,10 @@ import org.springframework.transaction.annotation.Transactional;
/** Adapts persisted account credentials and lifecycle state to Spring Security authentication. */
@Service
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class DatabaseUserDetailsService implements UserDetailsService {
private final AppUserRepository users;
DatabaseUserDetailsService(AppUserRepository users) {
this.users = users;
}
/**
* Loads the normalized account and disables authentication unless its lifecycle state is active.
*
@@ -9,6 +9,8 @@ import com.lab.labtimesheet.feature.integration.model.dto.SmtpForm;
import com.lab.labtimesheet.feature.integration.service.SmtpConfigurationService;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -25,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
*/
@Controller
@RequestMapping("/admin/smtp")
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class SmtpController {
private static final String TEST_FAILURE_MESSAGE =
"SMTP test failed. Verify the draft settings and server availability, then try again.";
@@ -41,11 +44,6 @@ class SmtpController {
private final SmtpConfigurationService smtp;
private final AccountService accounts;
SmtpController(SmtpConfigurationService smtp, AccountService accounts) {
this.smtp = smtp;
this.accounts = accounts;
}
@GetMapping
String form(Model model) {
return renderForm(model, null);
@@ -1,6 +1,8 @@
package com.lab.labtimesheet.feature.integration.controller;
import com.lab.labtimesheet.feature.integration.service.SmtpConfigurationService;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -9,13 +11,10 @@ import org.springframework.web.bind.annotation.ModelAttribute;
* configuration is active.
*/
@ControllerAdvice
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class SmtpWarningAdvice {
private final SmtpConfigurationService smtp;
SmtpWarningAdvice(SmtpConfigurationService smtp) {
this.smtp = smtp;
}
@ModelAttribute("smtpRestricted")
boolean smtpRestricted() {
return !smtp.hasActiveConfiguration();
@@ -2,18 +2,15 @@ package com.lab.labtimesheet.feature.integration.model.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Getter;
import lombok.Setter;
/** Validated identifier submitted by the SMTP test and activation forms. */
@Getter
@Setter
public class SmtpActionForm {
@NotNull(message = "SMTP draft is required")
@Positive(message = "SMTP draft is invalid")
private Long draftId;
public Long getDraftId() {
return draftId;
}
public void setDraftId(Long draftId) {
this.draftId = draftId;
}
}
@@ -8,11 +8,15 @@ import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* Validated Admin input for an SMTP draft. The cleartext password exists only for the current request and is
* cleared before the form is rendered again.
*/
@Getter
@Setter
public class SmtpForm {
@NotBlank(message = "Host is required")
@Size(max = 255, message = "Host must contain at most 255 characters")
@@ -96,18 +100,4 @@ public class SmtpForm {
return value == null || value.isEmpty() ? null : value;
}
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public SecurityMode getSecurityMode() { return securityMode; }
public void setSecurityMode(SecurityMode securityMode) { this.securityMode = securityMode; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getFromAddress() { return fromAddress; }
public void setFromAddress(String fromAddress) { this.fromAddress = fromAddress; }
public String getFromName() { return fromName; }
public void setFromName(String fromName) { this.fromName = fromName; }
}
@@ -15,6 +15,9 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Versioned SMTP configuration entity whose credentials remain AES-GCM encrypted at rest.
@@ -22,26 +25,33 @@ import jakarta.persistence.Version;
*/
@Entity
@Table(name = "smtp_configurations")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SmtpConfiguration {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16)
@Getter
private SmtpStatus status;
@Column(nullable = false, length = 255)
@Getter
private String host;
@Column(nullable = false)
@Getter
private int port;
@Enumerated(EnumType.STRING)
@Column(name = "security_mode", nullable = false, length = 16)
@Getter
private SecurityMode securityMode;
@Column(length = 320)
@Getter
private String username;
@Column(name = "password_ciphertext")
@@ -51,15 +61,19 @@ public class SmtpConfiguration {
private byte[] passwordNonce;
@Column(name = "secret_key_version")
@Getter
private Integer secretKeyVersion;
@Column(name = "from_address", nullable = false, length = 320)
@Getter
private String fromAddress;
@Column(name = "from_name", nullable = false, length = 120)
@Getter
private String fromName;
@Column(name = "tested_at")
@Getter
private Instant testedAt;
@Column(name = "tested_by_user_id")
@@ -89,10 +103,6 @@ public class SmtpConfiguration {
@Version
private long version;
/** Required by JPA; revisions are created through {@link #draft}. */
protected SmtpConfiguration() {
}
/**
* Creates an editable SMTP revision with encrypted credential material.
*
@@ -191,30 +201,6 @@ public class SmtpConfiguration {
return value == null || value.isBlank() ? null : value.trim();
}
public Long getId() {
return id;
}
public SmtpStatus getStatus() {
return status;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public SecurityMode getSecurityMode() {
return securityMode;
}
public String getUsername() {
return username;
}
/** @return a defensive copy of encrypted password bytes, or {@code null} */
public byte[] getPasswordCiphertext() {
return passwordCiphertext == null ? null : passwordCiphertext.clone();
@@ -225,19 +211,4 @@ public class SmtpConfiguration {
return passwordNonce == null ? null : passwordNonce.clone();
}
public Integer getSecretKeyVersion() {
return secretKeyVersion;
}
public String getFromAddress() {
return fromAddress;
}
public String getFromName() {
return fromName;
}
public Instant getTestedAt() {
return testedAt;
}
}
@@ -4,6 +4,8 @@ import com.lab.labtimesheet.feature.integration.model.dto.SmtpConnection;
import com.lab.labtimesheet.feature.integration.model.entity.SmtpConfiguration;
import com.lab.labtimesheet.feature.integration.model.SmtpStatus;
import com.lab.labtimesheet.feature.integration.repository.SmtpConfigurationRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -12,17 +14,12 @@ import org.springframework.transaction.annotation.Transactional;
* Stored credentials are decrypted only while constructing the immediate adapter call.
*/
@Service
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class MailDeliveryService {
private final SmtpConfigurationRepository configurations;
private final SecretCipher secrets;
private final SmtpProbe probe;
MailDeliveryService(SmtpConfigurationRepository configurations, SecretCipher secrets, SmtpProbe probe) {
this.configurations = configurations;
this.secrets = secrets;
this.probe = probe;
}
/**
* Reports whether workflows may emit required email.
*
@@ -11,6 +11,8 @@ import com.lab.labtimesheet.feature.integration.model.dto.SmtpDraft;
import com.lab.labtimesheet.feature.integration.model.dto.SmtpSetupStatus;
import com.lab.labtimesheet.feature.integration.model.entity.SmtpConfiguration;
import com.lab.labtimesheet.feature.integration.repository.SmtpConfigurationRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Service;
@@ -21,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
* A changed draft loses prior test status, and an active revision is retired when its tested successor activates.
*/
@Service
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class SmtpConfigurationService {
private final SmtpConfigurationRepository configurations;
private final AccountService accounts;
@@ -30,18 +33,6 @@ public class SmtpConfigurationService {
private final Clock clock;
private final MailDeliveryService mailDelivery;
SmtpConfigurationService(SmtpConfigurationRepository configurations, AccountService accounts,
SecretCipher secrets, SmtpProbe probe, Environment environment, Clock clock,
MailDeliveryService mailDelivery) {
this.configurations = configurations;
this.accounts = accounts;
this.secrets = secrets;
this.probe = probe;
this.environment = environment;
this.clock = clock;
this.mailDelivery = mailDelivery;
}
/**
* Creates or replaces the editable draft after validating Admin authority and environment transport rules.
* Any supplied password is encrypted before persistence and prior test status is cleared.