Remove service interfaces — use concrete classes directly

Each domain had a single-implementation interface (e.g. AdminService
interface + AdminServiceImpl). Merged implementation into the service
class and deleted the redundant interfaces per KISS principle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 11:04:41 +02:00
parent 03b96e8584
commit 9713412d42
21 changed files with 1171 additions and 1595 deletions

View File

@@ -1,21 +1,169 @@
package com.recipeapp.admin;
import com.recipeapp.admin.dto.*;
import com.recipeapp.admin.entity.AdminAuditLog;
import com.recipeapp.auth.UserAccountRepository;
import com.recipeapp.auth.entity.UserAccount;
import com.recipeapp.common.ConflictException;
import com.recipeapp.common.ResourceNotFoundException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.*;
public interface AdminService {
@Service
@Transactional
public class AdminService {
ListUsersResult listUsers(String search, Boolean isActive, int limit, int offset);
public record ListUsersResult(List<AdminUserResponse> users, long total) {}
AdminUserResponse createUser(CreateUserRequest request, String adminEmail);
AdminUserResponse updateUser(UUID userId, UpdateUserRequest request, String adminEmail);
private final UserAccountRepository userAccountRepository;
private final AdminAuditLogRepository auditLogRepository;
private final AdminUserQueryRepository adminUserQueryRepository;
private final PasswordEncoder passwordEncoder;
ResetPasswordResponse resetPassword(UUID userId, ResetPasswordRequest request, String adminEmail);
public AdminService(UserAccountRepository userAccountRepository,
AdminAuditLogRepository auditLogRepository,
AdminUserQueryRepository adminUserQueryRepository,
PasswordEncoder passwordEncoder) {
this.userAccountRepository = userAccountRepository;
this.auditLogRepository = auditLogRepository;
this.adminUserQueryRepository = adminUserQueryRepository;
this.passwordEncoder = passwordEncoder;
}
List<AuditLogResponse> listAuditLog(UUID targetUserId, int limit, int offset);
record ListUsersResult(List<AdminUserResponse> users, long total) {}
@Transactional(readOnly = true)
public ListUsersResult listUsers(String search, Boolean isActive, int limit, int offset) {
Pageable pageable = PageRequest.of(offset / limit, limit);
var users = adminUserQueryRepository.findUsersFiltered(search, isActive, pageable);
long total = adminUserQueryRepository.countUsersFiltered(search, isActive);
var responses = users.stream().map(this::toAdminUserResponse).toList();
return new ListUsersResult(responses, total);
}
public AdminUserResponse createUser(CreateUserRequest request, String adminEmail) {
if (userAccountRepository.existsByEmailIgnoreCase(request.email())) {
throw new ConflictException("A user with this email already exists");
}
var admin = resolveAdmin(adminEmail);
String hashedPassword = passwordEncoder.encode(request.tempPassword());
var user = new UserAccount(request.email(), request.displayName(), hashedPassword);
if (request.systemRole() != null) {
user.setSystemRole(request.systemRole());
}
user = userAccountRepository.save(user);
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), "create_account",
Map.of("email", request.email(), "displayName", request.displayName()), null));
return toAdminUserResponse(user);
}
public AdminUserResponse updateUser(UUID userId, UpdateUserRequest request, String adminEmail) {
var admin = resolveAdmin(adminEmail);
var user = userAccountRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
List<String> actions = new ArrayList<>();
Map<String, Object> detail = new HashMap<>();
if (request.displayName() != null) {
detail.put("displayName", request.displayName());
user.setDisplayName(request.displayName());
actions.add("update_account");
}
if (request.email() != null) {
if (!request.email().equalsIgnoreCase(user.getEmail())
&& userAccountRepository.existsByEmailIgnoreCase(request.email())) {
throw new ConflictException("A user with this email already exists");
}
detail.put("email", request.email());
user.setEmail(request.email());
actions.add("update_account");
}
if (request.systemRole() != null && !request.systemRole().equals(user.getSystemRole())) {
detail.put("systemRole", request.systemRole());
detail.put("previousSystemRole", user.getSystemRole());
user.setSystemRole(request.systemRole());
actions.add("change_system_role");
}
if (request.isActive() != null && request.isActive() != user.isActive()) {
detail.put("isActive", request.isActive());
user.setActive(request.isActive());
actions.add(request.isActive() ? "reactivate_account" : "deactivate_account");
}
user = userAccountRepository.save(user);
String action = actions.isEmpty() ? "update_account" : actions.getLast();
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), action, detail, null));
return toAdminUserResponse(user);
}
public ResetPasswordResponse resetPassword(UUID userId, ResetPasswordRequest request, String adminEmail) {
var admin = resolveAdmin(adminEmail);
var user = userAccountRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
user.setPasswordHash(passwordEncoder.encode(request.tempPassword()));
userAccountRepository.save(user);
Map<String, Object> detail = new HashMap<>();
if (request.reason() != null) {
detail.put("reason", request.reason());
}
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), "reset_password", detail, null));
return new ResetPasswordResponse("Password reset successfully", true);
}
@Transactional(readOnly = true)
public List<AuditLogResponse> listAuditLog(UUID targetUserId, int limit, int offset) {
Pageable pageable = PageRequest.of(offset / limit, limit);
List<AdminAuditLog> logs;
if (targetUserId != null) {
logs = auditLogRepository.findByTargetUserIdOrderByPerformedAtDesc(targetUserId, pageable);
} else {
logs = auditLogRepository.findAllByOrderByPerformedAtDesc(pageable);
}
return logs.stream().map(log -> {
String adminEmail = userAccountRepository.findById(log.getAdminId())
.map(UserAccount::getEmail).orElse(null);
String targetEmail = userAccountRepository.findById(log.getTargetUserId())
.map(UserAccount::getEmail).orElse(null);
return new AuditLogResponse(
log.getId(), log.getAdminId(), adminEmail,
log.getTargetUserId(), targetEmail,
log.getAction(), log.getDetail(), log.getPerformedAt());
}).toList();
}
private UserAccount resolveAdmin(String adminEmail) {
return userAccountRepository.findByEmailIgnoreCase(adminEmail)
.orElseThrow(() -> new ResourceNotFoundException("Admin user not found"));
}
private AdminUserResponse toAdminUserResponse(UserAccount user) {
return new AdminUserResponse(
user.getId(), user.getEmail(), user.getDisplayName(),
user.getSystemRole(), user.isActive(), user.getCreatedAt());
}
}

View File

@@ -1,166 +0,0 @@
package com.recipeapp.admin;
import com.recipeapp.admin.dto.*;
import com.recipeapp.admin.entity.AdminAuditLog;
import com.recipeapp.auth.UserAccountRepository;
import com.recipeapp.auth.entity.UserAccount;
import com.recipeapp.common.ConflictException;
import com.recipeapp.common.ResourceNotFoundException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
@Transactional
public class AdminServiceImpl implements AdminService {
private final UserAccountRepository userAccountRepository;
private final AdminAuditLogRepository auditLogRepository;
private final AdminUserQueryRepository adminUserQueryRepository;
private final PasswordEncoder passwordEncoder;
public AdminServiceImpl(UserAccountRepository userAccountRepository,
AdminAuditLogRepository auditLogRepository,
AdminUserQueryRepository adminUserQueryRepository,
PasswordEncoder passwordEncoder) {
this.userAccountRepository = userAccountRepository;
this.auditLogRepository = auditLogRepository;
this.adminUserQueryRepository = adminUserQueryRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
@Transactional(readOnly = true)
public ListUsersResult listUsers(String search, Boolean isActive, int limit, int offset) {
Pageable pageable = PageRequest.of(offset / limit, limit);
var users = adminUserQueryRepository.findUsersFiltered(search, isActive, pageable);
long total = adminUserQueryRepository.countUsersFiltered(search, isActive);
var responses = users.stream().map(this::toAdminUserResponse).toList();
return new ListUsersResult(responses, total);
}
@Override
public AdminUserResponse createUser(CreateUserRequest request, String adminEmail) {
if (userAccountRepository.existsByEmailIgnoreCase(request.email())) {
throw new ConflictException("A user with this email already exists");
}
var admin = resolveAdmin(adminEmail);
String hashedPassword = passwordEncoder.encode(request.tempPassword());
var user = new UserAccount(request.email(), request.displayName(), hashedPassword);
if (request.systemRole() != null) {
user.setSystemRole(request.systemRole());
}
user = userAccountRepository.save(user);
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), "create_account",
Map.of("email", request.email(), "displayName", request.displayName()), null));
return toAdminUserResponse(user);
}
@Override
public AdminUserResponse updateUser(UUID userId, UpdateUserRequest request, String adminEmail) {
var admin = resolveAdmin(adminEmail);
var user = userAccountRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
List<String> actions = new ArrayList<>();
Map<String, Object> detail = new HashMap<>();
if (request.displayName() != null) {
detail.put("displayName", request.displayName());
user.setDisplayName(request.displayName());
actions.add("update_account");
}
if (request.email() != null) {
if (!request.email().equalsIgnoreCase(user.getEmail())
&& userAccountRepository.existsByEmailIgnoreCase(request.email())) {
throw new ConflictException("A user with this email already exists");
}
detail.put("email", request.email());
user.setEmail(request.email());
actions.add("update_account");
}
if (request.systemRole() != null && !request.systemRole().equals(user.getSystemRole())) {
detail.put("systemRole", request.systemRole());
detail.put("previousSystemRole", user.getSystemRole());
user.setSystemRole(request.systemRole());
actions.add("change_system_role");
}
if (request.isActive() != null && request.isActive() != user.isActive()) {
detail.put("isActive", request.isActive());
user.setActive(request.isActive());
actions.add(request.isActive() ? "reactivate_account" : "deactivate_account");
}
user = userAccountRepository.save(user);
String action = actions.isEmpty() ? "update_account" : actions.getLast();
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), action, detail, null));
return toAdminUserResponse(user);
}
@Override
public ResetPasswordResponse resetPassword(UUID userId, ResetPasswordRequest request, String adminEmail) {
var admin = resolveAdmin(adminEmail);
var user = userAccountRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
user.setPasswordHash(passwordEncoder.encode(request.tempPassword()));
userAccountRepository.save(user);
Map<String, Object> detail = new HashMap<>();
if (request.reason() != null) {
detail.put("reason", request.reason());
}
auditLogRepository.save(new AdminAuditLog(
admin.getId(), user.getId(), "reset_password", detail, null));
return new ResetPasswordResponse("Password reset successfully", true);
}
@Override
@Transactional(readOnly = true)
public List<AuditLogResponse> listAuditLog(UUID targetUserId, int limit, int offset) {
Pageable pageable = PageRequest.of(offset / limit, limit);
List<AdminAuditLog> logs;
if (targetUserId != null) {
logs = auditLogRepository.findByTargetUserIdOrderByPerformedAtDesc(targetUserId, pageable);
} else {
logs = auditLogRepository.findAllByOrderByPerformedAtDesc(pageable);
}
return logs.stream().map(log -> {
String adminEmail = userAccountRepository.findById(log.getAdminId())
.map(UserAccount::getEmail).orElse(null);
String targetEmail = userAccountRepository.findById(log.getTargetUserId())
.map(UserAccount::getEmail).orElse(null);
return new AuditLogResponse(
log.getId(), log.getAdminId(), adminEmail,
log.getTargetUserId(), targetEmail,
log.getAction(), log.getDetail(), log.getPerformedAt());
}).toList();
}
private UserAccount resolveAdmin(String adminEmail) {
return userAccountRepository.findByEmailIgnoreCase(adminEmail)
.orElseThrow(() -> new ResourceNotFoundException("Admin user not found"));
}
private AdminUserResponse toAdminUserResponse(UserAccount user) {
return new AdminUserResponse(
user.getId(), user.getEmail(), user.getDisplayName(),
user.getSystemRole(), user.isActive(), user.getCreatedAt());
}
}