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:
@@ -1,13 +1,207 @@
|
||||
package com.recipeapp.household;
|
||||
|
||||
import com.recipeapp.auth.UserAccountRepository;
|
||||
import com.recipeapp.auth.entity.UserAccount;
|
||||
import com.recipeapp.common.ConflictException;
|
||||
import com.recipeapp.common.ResourceNotFoundException;
|
||||
import com.recipeapp.common.ValidationException;
|
||||
import com.recipeapp.household.dto.*;
|
||||
import com.recipeapp.household.entity.Household;
|
||||
import com.recipeapp.household.entity.HouseholdInvite;
|
||||
import com.recipeapp.household.entity.HouseholdMember;
|
||||
import com.recipeapp.planning.VarietyScoreConfigRepository;
|
||||
import com.recipeapp.planning.entity.VarietyScoreConfig;
|
||||
import com.recipeapp.recipe.IngredientCategoryRepository;
|
||||
import com.recipeapp.recipe.IngredientRepository;
|
||||
import com.recipeapp.recipe.TagRepository;
|
||||
import com.recipeapp.recipe.entity.Ingredient;
|
||||
import com.recipeapp.recipe.entity.IngredientCategory;
|
||||
import com.recipeapp.recipe.entity.Tag;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public interface HouseholdService {
|
||||
HouseholdResponse createHousehold(String userEmail, CreateHouseholdRequest request);
|
||||
HouseholdResponse getMyHousehold(String userEmail);
|
||||
List<MemberResponse> getMembers(String userEmail);
|
||||
InviteResponse createInvite(String userEmail);
|
||||
AcceptInviteResponse acceptInvite(String userEmail, String code);
|
||||
@Service
|
||||
public class HouseholdService {
|
||||
|
||||
private final UserAccountRepository userAccountRepository;
|
||||
private final HouseholdRepository householdRepository;
|
||||
private final HouseholdMemberRepository householdMemberRepository;
|
||||
private final HouseholdInviteRepository householdInviteRepository;
|
||||
private final IngredientRepository ingredientRepository;
|
||||
private final IngredientCategoryRepository ingredientCategoryRepository;
|
||||
private final TagRepository tagRepository;
|
||||
private final VarietyScoreConfigRepository varietyScoreConfigRepository;
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final String CODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
public HouseholdService(UserAccountRepository userAccountRepository,
|
||||
HouseholdRepository householdRepository,
|
||||
HouseholdMemberRepository householdMemberRepository,
|
||||
HouseholdInviteRepository householdInviteRepository,
|
||||
IngredientRepository ingredientRepository,
|
||||
IngredientCategoryRepository ingredientCategoryRepository,
|
||||
TagRepository tagRepository,
|
||||
VarietyScoreConfigRepository varietyScoreConfigRepository) {
|
||||
this.userAccountRepository = userAccountRepository;
|
||||
this.householdRepository = householdRepository;
|
||||
this.householdMemberRepository = householdMemberRepository;
|
||||
this.householdInviteRepository = householdInviteRepository;
|
||||
this.ingredientRepository = ingredientRepository;
|
||||
this.ingredientCategoryRepository = ingredientCategoryRepository;
|
||||
this.tagRepository = tagRepository;
|
||||
this.varietyScoreConfigRepository = varietyScoreConfigRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public HouseholdResponse createHousehold(String userEmail, CreateHouseholdRequest request) {
|
||||
UserAccount user = findUser(userEmail);
|
||||
|
||||
if (householdMemberRepository.findByUserEmailIgnoreCase(userEmail).isPresent()) {
|
||||
throw new ConflictException("User is already in a household");
|
||||
}
|
||||
|
||||
Household household = householdRepository.save(new Household(request.name(), user));
|
||||
HouseholdMember member = householdMemberRepository.save(
|
||||
new HouseholdMember(household, user, "planner"));
|
||||
|
||||
seedDefaultData(household);
|
||||
|
||||
return toHouseholdResponse(household, List.of(member));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public HouseholdResponse getMyHousehold(String userEmail) {
|
||||
HouseholdMember member = findMembership(userEmail);
|
||||
Household household = member.getHousehold();
|
||||
List<HouseholdMember> members = householdMemberRepository.findByHouseholdId(household.getId());
|
||||
return toHouseholdResponse(household, members);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<MemberResponse> getMembers(String userEmail) {
|
||||
HouseholdMember member = findMembership(userEmail);
|
||||
return householdMemberRepository.findByHouseholdId(member.getHousehold().getId())
|
||||
.stream()
|
||||
.map(this::toMemberResponse)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InviteResponse createInvite(String userEmail) {
|
||||
HouseholdMember member = findMembership(userEmail);
|
||||
Household household = member.getHousehold();
|
||||
|
||||
String code = generateInviteCode();
|
||||
Instant expiresAt = Instant.now().plusSeconds(48 * 3600);
|
||||
|
||||
HouseholdInvite invite = householdInviteRepository.save(
|
||||
new HouseholdInvite(household, code, expiresAt));
|
||||
|
||||
return new InviteResponse(
|
||||
invite.getInviteCode(),
|
||||
"https://yourapp.com/join/" + invite.getInviteCode(),
|
||||
invite.getExpiresAt());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AcceptInviteResponse acceptInvite(String userEmail, String code) {
|
||||
UserAccount user = findUser(userEmail);
|
||||
|
||||
if (householdMemberRepository.findByUserEmailIgnoreCase(userEmail).isPresent()) {
|
||||
throw new ConflictException("User is already in a household");
|
||||
}
|
||||
|
||||
HouseholdInvite invite = householdInviteRepository.findByInviteCode(code)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Invite not found"));
|
||||
|
||||
if ("used".equals(invite.getStatus())) {
|
||||
throw new ConflictException("Invite code already used");
|
||||
}
|
||||
if (invite.getExpiresAt().isBefore(Instant.now())) {
|
||||
throw new ValidationException("Invite code has expired");
|
||||
}
|
||||
|
||||
invite.setStatus("used");
|
||||
householdInviteRepository.save(invite);
|
||||
|
||||
Household household = invite.getHousehold();
|
||||
householdMemberRepository.save(new HouseholdMember(household, user, "member"));
|
||||
|
||||
return new AcceptInviteResponse(household.getId(), household.getName(), "member");
|
||||
}
|
||||
|
||||
private void seedDefaultData(Household household) {
|
||||
var categories = ingredientCategoryRepository.saveAll(List.of(
|
||||
new IngredientCategory(household, "Produce", (short) 1),
|
||||
new IngredientCategory(household, "Fish & Meat", (short) 2),
|
||||
new IngredientCategory(household, "Dairy & Eggs", (short) 3),
|
||||
new IngredientCategory(household, "Dry Goods & Pasta", (short) 4),
|
||||
new IngredientCategory(household, "Canned & Jarred", (short) 5),
|
||||
new IngredientCategory(household, "Sauces & Condiments", (short) 6),
|
||||
new IngredientCategory(household, "Frozen", (short) 7),
|
||||
new IngredientCategory(household, "Bakery & Bread", (short) 8)));
|
||||
|
||||
tagRepository.saveAll(List.of(
|
||||
new Tag(household, "Chicken", "protein"),
|
||||
new Tag(household, "Fish", "protein"),
|
||||
new Tag(household, "Beef", "protein"),
|
||||
new Tag(household, "Pork", "protein"),
|
||||
new Tag(household, "Vegetarian", "dietary"),
|
||||
new Tag(household, "Vegan", "dietary"),
|
||||
new Tag(household, "Pasta", "cuisine"),
|
||||
new Tag(household, "Quick meal", "other"),
|
||||
new Tag(household, "Child-friendly", "other")));
|
||||
|
||||
ingredientRepository.saveAll(List.of(
|
||||
new Ingredient(household, "Salt", true),
|
||||
new Ingredient(household, "Pepper", true),
|
||||
new Ingredient(household, "Olive oil", true),
|
||||
new Ingredient(household, "Butter", true),
|
||||
new Ingredient(household, "Garlic", true),
|
||||
new Ingredient(household, "Onion", true),
|
||||
new Ingredient(household, "Sugar", true),
|
||||
new Ingredient(household, "Flour", true),
|
||||
new Ingredient(household, "Rice", true),
|
||||
new Ingredient(household, "Pasta", true)));
|
||||
|
||||
varietyScoreConfigRepository.save(VarietyScoreConfig.defaults(household));
|
||||
}
|
||||
|
||||
private String generateInviteCode() {
|
||||
var sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private UserAccount findUser(String email) {
|
||||
return userAccountRepository.findByEmailIgnoreCase(email)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
private HouseholdMember findMembership(String email) {
|
||||
return householdMemberRepository.findByUserEmailIgnoreCase(email)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User is not in a household"));
|
||||
}
|
||||
|
||||
private HouseholdResponse toHouseholdResponse(Household household, List<HouseholdMember> members) {
|
||||
return new HouseholdResponse(
|
||||
household.getId(),
|
||||
household.getName(),
|
||||
members.stream().map(this::toMemberResponse).toList());
|
||||
}
|
||||
|
||||
private MemberResponse toMemberResponse(HouseholdMember member) {
|
||||
return new MemberResponse(
|
||||
member.getUser().getId(),
|
||||
member.getUser().getDisplayName(),
|
||||
member.getRole(),
|
||||
member.getJoinedAt());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user