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
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.danielagapov.spawn.user.api;

import com.danielagapov.spawn.shared.exceptions.Logger.ILogger;
import com.danielagapov.spawn.user.internal.services.IUserInterestService;
import com.danielagapov.spawn.shared.util.LoggingUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -16,57 +14,46 @@
public class UserInterestController {

private final IUserInterestService userInterestService;
private final ILogger logger;

@Autowired
public UserInterestController(IUserInterestService userInterestService, ILogger logger) {
public UserInterestController(IUserInterestService userInterestService) {
this.userInterestService = userInterestService;
this.logger = logger;
}

@GetMapping
public ResponseEntity<List<String>> getUserInterests(@PathVariable UUID userId) {
try {
List<String> interests = userInterestService.getUserInterests(userId);
interests = interests.stream()
.map(interest -> interest.replaceAll("^\"|\"$", ""))
.toList();
return ResponseEntity.ok(interests);
} catch (Exception e) {
logger.error("Error getting user interests for user: " + LoggingUtils.formatUserIdInfo(userId) + ": " + e.getMessage());
throw e;
}
List<String> interests = userInterestService.getUserInterests(userId);
return ResponseEntity.ok(interests);
}

@PutMapping
public ResponseEntity<List<String>> replaceUserInterests(
@PathVariable UUID userId,
@RequestBody List<String> interests) {
List<String> saved = userInterestService.replaceUserInterests(userId, interests);
return ResponseEntity.ok(saved);
}

@PostMapping
public ResponseEntity<String> addUserInterest(
@PathVariable UUID userId,
@RequestBody String userInterestName) {
userInterestName = userInterestName.replaceAll("^\"|\"$", "");
try {
String result = userInterestService.addUserInterest(userId, userInterestName);
return new ResponseEntity<>(result, HttpStatus.CREATED);
} catch (Exception e) {
logger.error("Error adding user interest for user: " + LoggingUtils.formatUserIdInfo(userId) + " - interest: " + userInterestName + ": " + e.getMessage());
throw e;
// @RequestBody with a plain JSON string arrives wrapped in quotes; strip them.
String cleaned = userInterestName.replaceAll("^\"|\"$", "").trim();
if (cleaned.isEmpty()) {
return ResponseEntity.badRequest().build();
}
String result = userInterestService.addUserInterest(userId, cleaned);
return new ResponseEntity<>(result, HttpStatus.CREATED);
}

@DeleteMapping("/{interest}")
public ResponseEntity<Void> removeUserInterest(
@PathVariable UUID userId,
@PathVariable String interest) {
try {
boolean removed = userInterestService.removeUserInterest(userId, interest);

if (removed) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} catch (Exception e) {
logger.error("Error removing user interest for user: " + LoggingUtils.formatUserIdInfo(userId) + " - interest: " + interest + ": " + e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
boolean removed = userInterestService.removeUserInterest(userId, interest);
return removed
? new ResponseEntity<>(HttpStatus.NO_CONTENT)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import com.danielagapov.spawn.user.internal.domain.UserInterest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
Expand All @@ -12,4 +15,11 @@
public interface UserInterestRepository extends JpaRepository<UserInterest, UUID> {
List<UserInterest> findByUserId(UUID userId);
Optional<UserInterest> findByUserIdAndInterest(UUID userId, String interest);

@Query("SELECT ui FROM UserInterest ui WHERE ui.user.id = :userId AND LOWER(ui.interest) = LOWER(:interest)")
Optional<UserInterest> findByUserIdAndInterestIgnoreCase(@Param("userId") UUID userId, @Param("interest") String interest);

@Modifying
@Query("DELETE FROM UserInterest ui WHERE ui.user.id = :userId")
void deleteAllByUserId(@Param("userId") UUID userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,8 @@
import java.util.UUID;

public interface IUserInterestService {
/**
* Get all interests for a user
* @param userId The ID of the user
* @return A list of user interest strings
*/
List<String> getUserInterests(UUID userId);

/**
* Add a new interest for a user
* @param user id and interest name
* @return The created user interest string
*/
String addUserInterest(UUID userId, String interestName);

/**
* Remove an interest for a user
* @param userId The ID of the user
* @param encodedInterestName The URL-encoded name of the interest to remove
* @return true if the interest was successfully removed, false if not found
*/
boolean removeUserInterest(UUID userId, String encodedInterestName);
boolean removeUserInterest(UUID userId, String interestName);
List<String> replaceUserInterests(UUID userId, List<String> interests);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

@Service
Expand All @@ -35,63 +32,64 @@ public UserInterestService(UserInterestRepository userInterestRepository, IUserR
@Override
@Cacheable(value = "userInterests", key = "#userId")
public List<String> getUserInterests(UUID userId) {
List<UserInterest> interests = userInterestRepository.findByUserId(userId);
return interests.stream()
return userInterestRepository.findByUserId(userId).stream()
.map(UserInterest::getInterest)
.collect(Collectors.toList());
}

@Override
@CacheEvict(value = "userInterests", key = "#userId")
public String addUserInterest(UUID userId, String interestName) {
String trimmed = interestName.trim();

Optional<UserInterest> existing = userInterestRepository.findByUserIdAndInterestIgnoreCase(userId, trimmed);
if (existing.isPresent()) {
return existing.get().getInterest();
}

User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found with id: " + userId));

UserInterest userInterest = new UserInterest(user, interestName);
UserInterest userInterest = new UserInterest(user, trimmed);
userInterest = userInterestRepository.save(userInterest);

return userInterest.getInterest();
}

@Override
@CacheEvict(value = "userInterests", key = "#userId")
public boolean removeUserInterest(UUID userId, String encodedInterestName) {
try {
// URL decode the interest name to handle spaces and special characters
String decodedInterest = URLDecoder.decode(encodedInterestName, StandardCharsets.UTF_8);

logger.info("Attempting to remove interest '" + decodedInterest + "' (encoded: '" + encodedInterestName + "') for user: " + LoggingUtils.formatUserIdInfo(userId));

// Debug: Log all existing interests for this user
List<UserInterest> allUserInterests = userInterestRepository.findByUserId(userId);
logger.info("User " + LoggingUtils.formatUserIdInfo(userId) + " currently has " + allUserInterests.size() + " interests:");
for (UserInterest existingInterest : allUserInterests) {
logger.info(" - '" + existingInterest.getInterest() + "' (length: " + existingInterest.getInterest().length() + ")");
}

Optional<UserInterest> userInterestOpt = userInterestRepository.findByUserIdAndInterest(userId, decodedInterest);

if (userInterestOpt.isPresent()) {
userInterestRepository.delete(userInterestOpt.get());
logger.info("Successfully removed interest '" + decodedInterest + "' for user: " + LoggingUtils.formatUserIdInfo(userId));
return true;
} else {
logger.warn("Interest '" + decodedInterest + "' not found for user: " + LoggingUtils.formatUserIdInfo(userId));
logger.warn("Exact search failed. Trying case-insensitive search...");

// Try case-insensitive search for debugging
for (UserInterest existingInterest : allUserInterests) {
if (existingInterest.getInterest().equalsIgnoreCase(decodedInterest)) {
logger.warn("Found case-insensitive match: '" + existingInterest.getInterest() + "' vs '" + decodedInterest + "'");
break;
}
}

return false;
public boolean removeUserInterest(UUID userId, String interestName) {
// Spring already URL-decodes @PathVariable, so no manual decoding needed.
// Use case-insensitive lookup to be resilient to casing mismatches.
Optional<UserInterest> userInterestOpt = userInterestRepository.findByUserIdAndInterestIgnoreCase(userId, interestName);

if (userInterestOpt.isPresent()) {
userInterestRepository.delete(userInterestOpt.get());
return true;
}

logger.warn("Interest '" + interestName + "' not found for user: " + LoggingUtils.formatUserIdInfo(userId));
return false;
}

@Override
@Transactional
@CacheEvict(value = "userInterests", key = "#userId")
public List<String> replaceUserInterests(UUID userId, List<String> interests) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found with id: " + userId));

userInterestRepository.deleteAllByUserId(userId);

List<String> saved = new ArrayList<>();
Set<String> seen = new HashSet<>();
for (String interest : interests) {
String trimmed = interest.trim();
if (!trimmed.isEmpty() && seen.add(trimmed.toLowerCase())) {
UserInterest entity = new UserInterest(user, trimmed);
userInterestRepository.save(entity);
saved.add(trimmed);
}
} catch (Exception e) {
logger.error("Error removing interest '" + encodedInterestName + "' for user: " + LoggingUtils.formatUserIdInfo(userId) + ": " + e.getMessage());
throw new RuntimeException("Failed to remove user interest", e);
}
return saved;
}
}
Loading