diff --git a/.cursor/rules.json b/.cursor/rules.json index 60256a05..6f745b46 100644 --- a/.cursor/rules.json +++ b/.cursor/rules.json @@ -789,77 +789,46 @@ "enforcement": "This rule is NON-NEGOTIABLE. Any file created in the wrong location must be immediately moved to its correct organized directory." }, "java_version_management": { - "description": "CRITICAL: This project requires Java 17, but the system may have Java 25 (or other versions) as default", + "description": "CRITICAL: This project requires Java 17, but the system has Java 25 as default which is INCOMPATIBLE", "project_java_version": "17", + "java_17_path": "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home", "mandatory_rules": [ - "ALWAYS set JAVA_HOME to Java 17 before running ANY Maven commands", - "NEVER assume the default Java version is correct", - "ALWAYS verify Java version before building, testing, or compiling", - "The project uses Java 17 features and Lombok 1.18.36 which is NOT compatible with Java 25" + "ALWAYS use Java 17 for ANY Maven command - the default Java 25 will NOT work", + "NEVER run ./mvnw commands without prefixing with JAVA_HOME", + "The project uses Lombok 1.18.36 which is NOT compatible with Java 21+", + "If you see Lombok errors, it's almost certainly a Java version issue" ], - "required_java_home_setup": { - "command": "export JAVA_HOME=$(/usr/libexec/java_home -v 17)", - "explanation": "Sets JAVA_HOME to Java 17 on macOS", - "when_to_use": "At the start of EVERY terminal session before any Maven command" - }, - "maven_commands_requiring_java_17": [ - "./mvnw clean compile", - "./mvnw test-compile", - "./mvnw clean test", - "./mvnw test", - "./mvnw clean install", - "./mvnw clean package", - "./mvnw spring-boot:run" - ], - "correct_command_pattern": { - "single_command": "export JAVA_HOME=$(/usr/libexec/java_home -v 17) && cd /Users/daggerpov/Documents/GitHub/Spawn-App-Back-End && ./mvnw clean test", - "explanation": "Always prefix Maven commands with JAVA_HOME setup in the same command to ensure correct Java version" + "simplest_command_pattern": { + "description": "Use inline JAVA_HOME assignment for simplicity", + "compile": "JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home ./mvnw clean compile", + "test_compile": "JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home ./mvnw test-compile", + "test": "JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home ./mvnw clean test", + "build_all": "JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home ./mvnw clean compile test-compile" }, "error_indicators": { - "lombok_error": "java.lang.NoSuchFieldException: com.sun.tools.javac.code.TypeTag :: UNKNOWN", - "compilation_error": "Fatal error compiling: java.lang.ExceptionInInitializerError", - "wrong_java_message": "These errors typically indicate Java version mismatch (using Java 25 instead of Java 17)" + "lombok_typetag_error": "java.lang.NoSuchFieldException: com.sun.tools.javac.code.TypeTag :: UNKNOWN", + "initialization_error": "Fatal error compiling: java.lang.ExceptionInInitializerError", + "cause": "These errors mean you ran Maven with Java 25 instead of Java 17" }, - "debugging_workflow": { - "step_1": "Check current Java version: java -version", - "step_2": "If not Java 17, set JAVA_HOME: export JAVA_HOME=$(/usr/libexec/java_home -v 17)", - "step_3": "Verify change: java -version (should show Java 17)", - "step_4": "Re-run the Maven command" - }, - "available_java_versions_on_system": [ - "Java 25 (default, but INCOMPATIBLE)", - "Java 23 (available, but INCOMPATIBLE)", - "Java 17 (REQUIRED for this project)", - "Java 11 (available, but too old)" + "quick_fix_workflow": [ + "1. If you see Lombok/TypeTag errors, DO NOT try to fix Lombok", + "2. Simply re-run the command with: JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home ./mvnw ", + "3. The build should succeed immediately" ], + "available_java_versions_on_system": { + "java_25": "/Users/daggerpov/Library/Java/JavaVirtualMachines/openjdk-25/Contents/Home (DEFAULT - DO NOT USE)", + "java_17": "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home (USE THIS)" + }, "why_java_17": [ - "Project is configured for Java 17 in pom.xml (java.version=17)", - "Lombok 1.18.36 has compatibility issues with Java 21+", - "Spring Boot 3.3.5 works best with Java 17", - "All dependencies are tested with Java 17" + "pom.xml specifies java.version=17", + "Lombok 1.18.36 does not support Java 21+", + "Spring Boot 3.3.5 is optimized for Java 17" ], - "automation_principle": { - "rule": "ALWAYS include JAVA_HOME setup in the same command as Maven execution", - "reason": "Prevents forgetting to set JAVA_HOME and encountering compilation errors", - "pattern": "export JAVA_HOME=$(/usr/libexec/java_home -v 17) && cd && ./mvnw " - }, - "testing_workflow": { - "step_1_check_java": "export JAVA_HOME=$(/usr/libexec/java_home -v 17) && java -version", - "step_2_build": "cd /Users/daggerpov/Documents/GitHub/Spawn-App-Back-End && ./mvnw clean test", - "step_3_verify": "Ensure 'BUILD SUCCESS' with no Java version errors" - }, "forbidden_actions": [ - "NEVER run Maven commands without setting JAVA_HOME first", - "NEVER assume java -version shows Java 17 by default", - "NEVER try to fix Lombok errors without checking Java version first", - "NEVER update Lombok version to work with Java 25 (use Java 17 instead)" + "NEVER run ./mvnw without JAVA_HOME prefix", + "NEVER try to upgrade Lombok to fix Java 25 compatibility", + "NEVER assume the default java -version is correct" ], - "quick_reference": { - "verify_java": "java -version (should show Java 17)", - "set_java_17": "export JAVA_HOME=$(/usr/libexec/java_home -v 17)", - "list_available": "/usr/libexec/java_home -V", - "build_with_correct_java": "export JAVA_HOME=$(/usr/libexec/java_home -v 17) && ./mvnw clean test" - }, - "enforcement": "This is MANDATORY. Java version issues waste time and cause confusing errors. ALWAYS set JAVA_HOME to Java 17 before ANY Maven command." + "enforcement": "MANDATORY: Prefix ALL Maven commands with JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" } } \ No newline at end of file diff --git a/src/main/java/com/danielagapov/spawn/activity/api/ActivityController.java b/src/main/java/com/danielagapov/spawn/activity/api/ActivityController.java index 33814401..52f9b632 100644 --- a/src/main/java/com/danielagapov/spawn/activity/api/ActivityController.java +++ b/src/main/java/com/danielagapov/spawn/activity/api/ActivityController.java @@ -1,5 +1,6 @@ package com.danielagapov.spawn.activity.api; +import com.danielagapov.spawn.activity.api.dto.ActivityCreationResponseDTO; import com.danielagapov.spawn.activity.api.dto.ActivityDTO; import com.danielagapov.spawn.activity.api.dto.ActivityPartialUpdateDTO; import com.danielagapov.spawn.activity.api.dto.FullFeedActivityDTO; @@ -70,16 +71,18 @@ public ResponseEntity getProfileActivities(@PathVariable UUID profileUserId, // full path: /api/v1/activities @PostMapping - public ResponseEntity createActivity(@RequestBody ActivityDTO activityDTO) { + public ResponseEntity createActivity(@RequestBody ActivityDTO activityDTO) { try { - FullFeedActivityDTO response = activityService.createActivityWithSuggestions(activityDTO); + FullFeedActivityDTO createdActivity = activityService.createActivityWithSuggestions(activityDTO); + // Wrap in ActivityCreationResponseDTO to match iOS expected structure + ActivityCreationResponseDTO response = new ActivityCreationResponseDTO(createdActivity); return new ResponseEntity<>(response, HttpStatus.CREATED); } catch (IllegalArgumentException e) { logger.error("Invalid request for activity creation: " + e.getMessage()); - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity(HttpStatus.BAD_REQUEST); } catch (BaseNotFoundException e) { logger.error("Entity not found during activity creation: " + e.getMessage()); - return new ResponseEntity(HttpStatus.NOT_FOUND); + return new ResponseEntity(HttpStatus.NOT_FOUND); } catch (Exception e) { logger.error("Error creating activity: " + e.getMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityCreationResponseDTO.java b/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityCreationResponseDTO.java new file mode 100644 index 00000000..3497288d --- /dev/null +++ b/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityCreationResponseDTO.java @@ -0,0 +1,29 @@ +package com.danielagapov.spawn.activity.api.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Response DTO for activity creation that wraps the created activity + * and optionally includes friend suggestions for activity types. + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ActivityCreationResponseDTO { + private FullFeedActivityDTO activity; + private ActivityTypeFriendSuggestionDTO friendSuggestion; + + /** + * Create a response with just the activity (no friend suggestion) + */ + public ActivityCreationResponseDTO(FullFeedActivityDTO activity) { + this.activity = activity; + this.friendSuggestion = null; + } +} diff --git a/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityTypeDTO.java b/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityTypeDTO.java index abe9a4b4..73108104 100644 --- a/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityTypeDTO.java +++ b/src/main/java/com/danielagapov/spawn/activity/api/dto/ActivityTypeDTO.java @@ -1,6 +1,6 @@ package com.danielagapov.spawn.activity.api.dto; -import com.danielagapov.spawn.user.api.dto.BaseUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,6 +10,13 @@ import java.util.List; import java.util.UUID; +/** + * DTO for activity types. + * + * Note: associatedFriends uses MinimalFriendDTO instead of BaseUserDTO to reduce memory usage. + * MinimalFriendDTO only contains essential fields (id, username, name, profilePicture) + * needed for displaying friends in activity type selection UI. + */ @Getter @Setter @NoArgsConstructor @@ -17,7 +24,7 @@ public class ActivityTypeDTO implements Serializable { private UUID id; private String title; - private List associatedFriends; + private List associatedFriends; private String icon; private int orderNum; private UUID ownerUserId; diff --git a/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityService.java b/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityService.java index e40ae2f0..c1da3ec0 100644 --- a/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityService.java +++ b/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityService.java @@ -1324,8 +1324,8 @@ public List getPastActivitiesWhereUserInvited(UUID inviterUs } /** - * Gets feed Activities for a profile. If the profile user has no upcoming Activities, returns past Activities - * that the profile user invited the requesting user to, with a flag indicating they are past Activities. + * Gets Activities for a profile where the requesting user was invited or is participating. + * Includes both upcoming and past Activities, each flagged appropriately. * * @param profileUserId The user ID of the profile being viewed * @param requestingUserId The user ID of the user viewing the profile @@ -1334,32 +1334,88 @@ public List getPastActivitiesWhereUserInvited(UUID inviterUs @Override public List getProfileActivities(UUID profileUserId, UUID requestingUserId) { try { - // Get upcoming Activities created by the profile user - List upcomingActivities = getActivitiesByOwnerId(profileUserId); - List upcomingFullActivities = convertActivitiesToFullFeedSelfOwnedActivities(upcomingActivities, requestingUserId); + // Get ALL Activities created by the profile user + List allActivities = getActivitiesByOwnerId(profileUserId); + List allFullActivities = convertActivitiesToFullFeedSelfOwnedActivities(allActivities, requestingUserId); - // Remove expired Activities - List nonExpiredActivities = removeExpiredActivities(upcomingFullActivities); + // Filter to only include activities where the requesting user is invited or participating + List filteredActivities = allFullActivities.stream() + .filter(activity -> isUserInvitedOrParticipating(activity, requestingUserId)) + .collect(Collectors.toList()); - // Convert to ProfileActivityDTO + // Convert to ProfileActivityDTO with proper past/upcoming flag List result = new ArrayList<>(); + List upcomingActivities = new ArrayList<>(); + List pastActivities = new ArrayList<>(); - // If there are upcoming Activities, return them as ProfileActivityDTOs - if (!nonExpiredActivities.isEmpty()) { - sortActivitiesByStartTime(nonExpiredActivities); - for (FullFeedActivityDTO Activity : nonExpiredActivities) { - result.add(ProfileActivityDTO.fromFullFeedActivityDTO(Activity, false)); // false = not past activity + for (FullFeedActivityDTO activity : filteredActivities) { + boolean isExpired = expirationService.isActivityExpired( + activity.getStartTime(), + activity.getEndTime(), + activity.getCreatedAt(), + activity.getClientTimezone() + ); + + ProfileActivityDTO profileActivity = ProfileActivityDTO.fromFullFeedActivityDTO(activity, isExpired); + + if (isExpired) { + pastActivities.add(profileActivity); + } else { + upcomingActivities.add(profileActivity); } - return result; } - // If no upcoming Activities, get past Activities where the profile user invited the requesting user - return getPastActivitiesWhereUserInvited(profileUserId, requestingUserId); + // Sort upcoming activities by start time (soonest first) + upcomingActivities.sort(Comparator.comparing( + ProfileActivityDTO::getStartTime, + Comparator.nullsLast(Comparator.naturalOrder()) + )); + + // Sort past activities by start time (most recent first) + pastActivities.sort(Comparator.comparing( + ProfileActivityDTO::getStartTime, + Comparator.nullsLast(Comparator.reverseOrder()) + )); + + // Combine: upcoming first, then past + result.addAll(upcomingActivities); + result.addAll(pastActivities); + + return result; } catch (Exception e) { logger.error("Error fetching profile Activities for user " + profileUserId + " requested by " + requestingUserId + ": " + e.getMessage()); throw e; } } + + /** + * Checks if the requesting user is invited to or participating in the activity. + * + * @param activity The activity to check + * @param requestingUserId The user ID to check for + * @return true if the user is in invitedUsers or participantUsers + */ + private boolean isUserInvitedOrParticipating(FullFeedActivityDTO activity, UUID requestingUserId) { + // Check if user is in invited users + if (activity.getInvitedUsers() != null) { + for (BaseUserDTO user : activity.getInvitedUsers()) { + if (user.getId().equals(requestingUserId)) { + return true; + } + } + } + + // Check if user is in participant users + if (activity.getParticipantUsers() != null) { + for (BaseUserDTO user : activity.getParticipantUsers()) { + if (user.getId().equals(requestingUserId)) { + return true; + } + } + } + + return false; + } } diff --git a/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityTypeService.java b/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityTypeService.java index b7a9e133..9389717d 100644 --- a/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityTypeService.java +++ b/src/main/java/com/danielagapov/spawn/activity/internal/services/ActivityTypeService.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; import com.danielagapov.spawn.user.api.dto.AbstractUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; @Service @AllArgsConstructor @@ -425,7 +426,7 @@ private ActivityType convertDTOToEntityWithFriendLookup(ActivityTypeDTO dto, Use // Get associated friends from database instead of creating detached entities List associatedFriends = new ArrayList<>(); if (dto.getAssociatedFriends() != null && !dto.getAssociatedFriends().isEmpty()) { - for (BaseUserDTO friendDTO : dto.getAssociatedFriends()) { + for (MinimalFriendDTO friendDTO : dto.getAssociatedFriends()) { try { User friend = userService.getUserEntityById(friendDTO.getId()); associatedFriends.add(friend); diff --git a/src/main/java/com/danielagapov/spawn/auth/internal/services/AuthService.java b/src/main/java/com/danielagapov/spawn/auth/internal/services/AuthService.java index 45cf43ac..2b3163d1 100644 --- a/src/main/java/com/danielagapov/spawn/auth/internal/services/AuthService.java +++ b/src/main/java/com/danielagapov/spawn/auth/internal/services/AuthService.java @@ -172,7 +172,23 @@ public boolean changePassword(String username, String currentPassword, String ne public AuthResponseDTO getUserByToken(String token) { final String username = jwtService.extractUsername(token); User user = userService.getUserEntityByUsername(username); - return UserMapper.toAuthResponseDTO(user, oauthService.isOAuthUser(user.getId())); + + // Determine the auth provider for this user + boolean isOAuthUser = oauthService.isOAuthUser(user.getId()); + String provider; + if (isOAuthUser) { + try { + OAuthProvider oauthProvider = oauthService.getOAuthProvider(user.getId()); + provider = oauthProvider.name(); // "google" or "apple" + } catch (Exception e) { + logger.warn("Could not determine OAuth provider for user: " + user.getId() + ". " + e.getMessage()); + provider = null; + } + } else { + provider = "email"; + } + + return UserMapper.toAuthResponseDTO(user, isOAuthUser, provider); } @Override diff --git a/src/main/java/com/danielagapov/spawn/shared/events/UserSearchEvents.java b/src/main/java/com/danielagapov/spawn/shared/events/UserSearchEvents.java index 89ff38cf..408a2463 100644 --- a/src/main/java/com/danielagapov/spawn/shared/events/UserSearchEvents.java +++ b/src/main/java/com/danielagapov/spawn/shared/events/UserSearchEvents.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.shared.events; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.SearchedUserResult; import java.util.List; diff --git a/src/main/java/com/danielagapov/spawn/shared/util/ActivityTypeMapper.java b/src/main/java/com/danielagapov/spawn/shared/util/ActivityTypeMapper.java index eac3f6ac..1be83ee5 100644 --- a/src/main/java/com/danielagapov/spawn/shared/util/ActivityTypeMapper.java +++ b/src/main/java/com/danielagapov/spawn/shared/util/ActivityTypeMapper.java @@ -2,6 +2,7 @@ import com.danielagapov.spawn.activity.api.dto.ActivityTypeDTO; import com.danielagapov.spawn.activity.internal.domain.ActivityType; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; import com.danielagapov.spawn.user.internal.domain.User; import java.util.Collections; @@ -10,11 +11,20 @@ public final class ActivityTypeMapper { + /** + * Convert ActivityTypeDTO to ActivityType entity. + * Note: Uses MinimalFriendDTO for associatedFriends to reduce memory usage. + */ public static ActivityType toEntity(ActivityTypeDTO dto, User creator) { + // Convert MinimalFriendDTOs to User entities + List associatedFriendEntities = dto.getAssociatedFriends() != null + ? UserMapper.toEntityList(dto.getAssociatedFriends()) + : Collections.emptyList(); + return new ActivityType( dto.getId(), dto.getTitle(), - dto.getAssociatedFriends() != null ? UserMapper.toEntityList(dto.getAssociatedFriends()) : Collections.emptyList(), + associatedFriendEntities, creator, dto.getOrderNum(), dto.getIcon(), @@ -34,11 +44,20 @@ public static List toDTOList(List entities) { .toList(); } + /** + * Convert ActivityType entity to ActivityTypeDTO. + * Uses MinimalFriendDTO for associatedFriends to reduce memory usage. + */ public static ActivityTypeDTO toDTO(ActivityType entity) { + // Convert User entities to MinimalFriendDTOs for memory efficiency + List minimalFriends = entity.getAssociatedFriends() != null + ? UserMapper.toMinimalFriendDTOList(entity.getAssociatedFriends()) + : Collections.emptyList(); + return new ActivityTypeDTO( entity.getId(), entity.getTitle(), - entity.getAssociatedFriends() != null ? UserMapper.toDTOList(entity.getAssociatedFriends()) : Collections.emptyList(), + minimalFriends, entity.getIcon(), entity.getOrderNum(), entity.getCreator().getId(), diff --git a/src/main/java/com/danielagapov/spawn/shared/util/FriendUserMapper.java b/src/main/java/com/danielagapov/spawn/shared/util/FriendUserMapper.java index 392da21c..b3e201b9 100644 --- a/src/main/java/com/danielagapov/spawn/shared/util/FriendUserMapper.java +++ b/src/main/java/com/danielagapov/spawn/shared/util/FriendUserMapper.java @@ -1,6 +1,6 @@ package com.danielagapov.spawn.shared.util; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.UserRelationshipType; import com.danielagapov.spawn.user.internal.domain.User; diff --git a/src/main/java/com/danielagapov/spawn/shared/util/UserMapper.java b/src/main/java/com/danielagapov/spawn/shared/util/UserMapper.java index 53979fe1..d6ccd0be 100644 --- a/src/main/java/com/danielagapov/spawn/shared/util/UserMapper.java +++ b/src/main/java/com/danielagapov/spawn/shared/util/UserMapper.java @@ -2,6 +2,7 @@ import com.danielagapov.spawn.user.api.dto.AuthResponseDTO; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; import com.danielagapov.spawn.user.api.dto.UserCreationDTO; import com.danielagapov.spawn.user.api.dto.UserDTO; import com.danielagapov.spawn.user.internal.domain.User; @@ -21,7 +22,21 @@ public static BaseUserDTO toDTO(User user) { user.getUsername(), user.getBio(), user.getProfilePictureUrlString(), - user.getHasCompletedOnboarding() + user.getHasCompletedOnboarding(), + null // provider not specified + ); + } + + public static BaseUserDTO toDTOWithProvider(User user, String provider) { + return new BaseUserDTO( + user.getId(), + user.getName(), + user.getEmail(), + user.getUsername(), + user.getBio(), + user.getProfilePictureUrlString(), + user.getHasCompletedOnboarding(), + provider ); } @@ -35,10 +50,59 @@ public static AuthResponseDTO toAuthResponseDTO(User user, boolean isOAuthUser) return new AuthResponseDTO(baseUserDTO, user.getStatus(), isOAuthUser); } + public static AuthResponseDTO toAuthResponseDTO(User user, boolean isOAuthUser, String provider) { + BaseUserDTO baseUserDTO = toDTOWithProvider(user, provider); + return new AuthResponseDTO(baseUserDTO, user.getStatus(), isOAuthUser); + } + public static List toDTOList(List users) { return users.stream().map(UserMapper::toDTO).toList(); } + /** + * Convert User entity to MinimalFriendDTO with only essential fields. + * This reduces memory usage when displaying friends in selection lists. + */ + public static MinimalFriendDTO toMinimalFriendDTO(User user) { + return new MinimalFriendDTO( + user.getId(), + user.getUsername(), + user.getName(), + user.getProfilePictureUrlString() + ); + } + + /** + * Convert list of User entities to MinimalFriendDTO list. + */ + public static List toMinimalFriendDTOList(List users) { + return users.stream().map(UserMapper::toMinimalFriendDTO).toList(); + } + + /** + * Convert MinimalFriendDTO to User entity (for conversion operations). + * WARNING: This creates an incomplete User entity - only id, username, name, profilePicture are set. + */ + public static User toEntity(MinimalFriendDTO dto) { + return new User( + dto.getId(), + dto.getUsername(), + dto.getProfilePicture(), + dto.getName(), + null, // bio not available in MinimalFriendDTO + null // email not available in MinimalFriendDTO + ); + } + + /** + * Convert list of MinimalFriendDTO to list of User entities. + */ + public static List toEntityList(List dtos) { + return dtos.stream() + .map(UserMapper::toEntity) + .collect(Collectors.toList()); + } + public static UserDTO toDTO(User user, List friendUserIds) { return new UserDTO( @@ -78,7 +142,7 @@ public static List toDTOList(List users, Map> fr .collect(Collectors.toList()); } - public static List toEntityList(List userDTOs) { + public static List toEntityListFromBaseUserDTOs(List userDTOs) { return userDTOs.stream() .map(UserMapper::toEntity) .collect(Collectors.toList()); diff --git a/src/main/java/com/danielagapov/spawn/user/api/UserController.java b/src/main/java/com/danielagapov/spawn/user/api/UserController.java index 03d8634b..84da59c0 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/UserController.java +++ b/src/main/java/com/danielagapov/spawn/user/api/UserController.java @@ -1,8 +1,9 @@ package com.danielagapov.spawn.user.api; import com.danielagapov.spawn.user.api.dto.*; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; -import com.danielagapov.spawn.user.api.dto.UserProfileInfoDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserProfileInfoDTO; import com.danielagapov.spawn.user.api.dto.ContactCrossReferenceRequestDTO; import com.danielagapov.spawn.user.api.dto.ContactCrossReferenceResponseDTO; import com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException; @@ -67,6 +68,28 @@ public ResponseEntity> getUserFriends(@PathVaria } } + // full path: /api/v1/users/friends-minimal/{id} + // Returns minimal friend data (id, username, name, profilePicture) to reduce memory usage + // Use this for friend selection lists in activity creation and activity type management + @GetMapping("friends-minimal/{id}") + public ResponseEntity> getUserFriendsMinimal(@PathVariable UUID id) { + if (id == null) { + logger.error("Invalid parameter: user ID is null"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + try { + List friends = userService.getMinimalFriendUsersByUserId(id); + List filteredFriends = blockedUserService.filterOutBlockedUsers(friends, id); + return new ResponseEntity<>(filteredFriends, HttpStatus.OK); + } catch (BaseNotFoundException e) { + logger.error("User not found for minimal friends retrieval: " + LoggingUtils.formatUserIdInfo(id) + ": " + e.getMessage()); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } catch (Exception e) { + logger.error("Error getting minimal friends for user: " + LoggingUtils.formatUserIdInfo(id) + ": " + e.getMessage()); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + // full path: /api/v1/users/{id} @GetMapping("{id}") public ResponseEntity getUser(@PathVariable UUID id) { diff --git a/src/main/java/com/danielagapov/spawn/user/api/UserSocialMediaController.java b/src/main/java/com/danielagapov/spawn/user/api/UserSocialMediaController.java index ff2fb904..719d327d 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/UserSocialMediaController.java +++ b/src/main/java/com/danielagapov/spawn/user/api/UserSocialMediaController.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.user.api; -import com.danielagapov.spawn.user.api.dto.UpdateUserSocialMediaDTO; -import com.danielagapov.spawn.user.api.dto.UserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UpdateUserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserSocialMediaDTO; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; import com.danielagapov.spawn.user.internal.services.IUserSocialMediaService; import com.danielagapov.spawn.shared.util.LoggingUtils; diff --git a/src/main/java/com/danielagapov/spawn/user/api/UserStatsController.java b/src/main/java/com/danielagapov/spawn/user/api/UserStatsController.java index 76e0ff24..4cb7b2aa 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/UserStatsController.java +++ b/src/main/java/com/danielagapov/spawn/user/api/UserStatsController.java @@ -1,6 +1,6 @@ package com.danielagapov.spawn.user.api; -import com.danielagapov.spawn.user.api.dto.UserStatsDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserStatsDTO; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; import com.danielagapov.spawn.user.internal.services.IUserStatsService; import com.danielagapov.spawn.shared.util.LoggingUtils; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/BaseUserDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/BaseUserDTO.java index cce72947..cce9aa95 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/BaseUserDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/BaseUserDTO.java @@ -14,11 +14,20 @@ public class BaseUserDTO extends AbstractUserDTO { private String profilePicture; private Boolean hasCompletedOnboarding; + private String provider; // Auth provider: "google", "apple", or "email" public BaseUserDTO(UUID id, String name, String email, String username, String bio, String profilePicture) { super(id, name, email, username, bio); this.profilePicture = profilePicture; this.hasCompletedOnboarding = false; // Default value for backward compatibility + this.provider = null; + } + + public BaseUserDTO(UUID id, String name, String email, String username, String bio, String profilePicture, Boolean hasCompletedOnboarding) { + super(id, name, email, username, bio); + this.profilePicture = profilePicture; + this.hasCompletedOnboarding = hasCompletedOnboarding != null ? hasCompletedOnboarding : false; + this.provider = null; } @JsonCreator @@ -29,9 +38,11 @@ public BaseUserDTO( @JsonProperty("username") String username, @JsonProperty("bio") String bio, @JsonProperty("profilePicture") String profilePicture, - @JsonProperty("hasCompletedOnboarding") Boolean hasCompletedOnboarding) { + @JsonProperty("hasCompletedOnboarding") Boolean hasCompletedOnboarding, + @JsonProperty("provider") String provider) { super(id, name, email, username, bio); this.profilePicture = profilePicture; this.hasCompletedOnboarding = hasCompletedOnboarding != null ? hasCompletedOnboarding : false; + this.provider = provider; } } diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/FullFriendUserDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/FullFriendUserDTO.java index 9a823fa4..7eb069d4 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/FullFriendUserDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/FullFriendUserDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.FriendUser; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; import lombok.Getter; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/MinimalFriendDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/MinimalFriendDTO.java new file mode 100644 index 00000000..62b7b237 --- /dev/null +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/MinimalFriendDTO.java @@ -0,0 +1,58 @@ +package com.danielagapov.spawn.user.api.dto.FriendUser; + +import com.danielagapov.spawn.user.api.dto.BaseUserDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.UUID; + +/** + * A minimal DTO for friend users, containing only the essential fields needed + * for displaying friends in selection lists (e.g., activity creation, activity types). + * + * This DTO significantly reduces memory usage compared to FullFriendUserDTO by + * excluding fields like bio and email that are unnecessary for friend selection UIs. + * + * Fields included: + * - id: Required for selection/identification + * - username: Displayed as @username + * - name: Displayed as the friend's name + * - profilePicture: URL for avatar display + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class MinimalFriendDTO implements Serializable { + private UUID id; + private String username; + private String name; + private String profilePicture; + + /** + * Creates a MinimalFriendDTO from a FullFriendUserDTO + */ + public static MinimalFriendDTO fromFullFriendUserDTO(FullFriendUserDTO fullFriend) { + return new MinimalFriendDTO( + fullFriend.getId(), + fullFriend.getUsername(), + fullFriend.getName(), + fullFriend.getProfilePicture() + ); + } + + /** + * Creates a MinimalFriendDTO from a BaseUserDTO + */ + public static MinimalFriendDTO fromBaseUserDTO(BaseUserDTO baseUser) { + return new MinimalFriendDTO( + baseUser.getId(), + baseUser.getUsername(), + baseUser.getName(), + baseUser.getProfilePicture() + ); + } +} diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/RecommendedFriendUserDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/RecommendedFriendUserDTO.java index d316af0e..4861564a 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/RecommendedFriendUserDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/FriendUser/RecommendedFriendUserDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.FriendUser; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; import com.danielagapov.spawn.shared.util.UserRelationshipType; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UpdateUserSocialMediaDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UpdateUserSocialMediaDTO.java index 6adf6d53..f4644a5e 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UpdateUserSocialMediaDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UpdateUserSocialMediaDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.Profile; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserProfileInfoDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserProfileInfoDTO.java index 87eaaf38..740d122b 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserProfileInfoDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserProfileInfoDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.Profile; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserSocialMediaDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserSocialMediaDTO.java index e0a9cc20..ddfab721 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserSocialMediaDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserSocialMediaDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.Profile; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserStatsDTO.java b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserStatsDTO.java index 87d4fe96..08853b19 100644 --- a/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserStatsDTO.java +++ b/src/main/java/com/danielagapov/spawn/user/api/dto/Profile/UserStatsDTO.java @@ -1,4 +1,4 @@ -package com.danielagapov.spawn.user.api.dto; +package com.danielagapov.spawn.user.api.dto.Profile; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserFriendshipQueryService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserFriendshipQueryService.java index a09c781a..3eb4b3ed 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserFriendshipQueryService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserFriendshipQueryService.java @@ -1,6 +1,7 @@ package com.danielagapov.spawn.user.internal.services; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; import com.danielagapov.spawn.user.internal.domain.User; import java.util.List; @@ -42,6 +43,19 @@ public interface IUserFriendshipQueryService { */ List getFullFriendUsersByUserId(UUID requestingUserId); + /** + * Retrieves all friends of a user as MinimalFriendDTO objects with only essential fields. + * This is optimized for friend selection lists (activity creation, activity types) to reduce memory usage. + * + * Fields included: id, username, name, profilePicture + * Fields excluded: bio, email, hasCompletedOnboarding, provider + * + * @param requestingUserId the unique identifier of the user requesting their friends + * @return List of MinimalFriendDTO objects representing the user's friends + * @throws com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException if user doesn't exist + */ + List getMinimalFriendUsersByUserId(UUID requestingUserId); + /** * Checks if a user is a friend of another user. * diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchQueryService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchQueryService.java index 234dba47..83e3b704 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchQueryService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchQueryService.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.user.internal.services; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.SearchedUserResult; import java.util.List; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchService.java index 69270ab8..f0139e6c 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSearchService.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.user.internal.services; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.SearchedUserResult; import java.util.List; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserService.java index bbb7eaa7..2bc2077f 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserService.java @@ -1,9 +1,10 @@ package com.danielagapov.spawn.user.internal.services; import com.danielagapov.spawn.user.api.dto.*; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; -import com.danielagapov.spawn.user.api.dto.UserProfileInfoDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserProfileInfoDTO; import com.danielagapov.spawn.shared.util.UserStatus; import com.danielagapov.spawn.user.internal.domain.User; import com.danielagapov.spawn.shared.util.SearchedUserResult; @@ -125,6 +126,19 @@ public interface IUserService { */ List getFullFriendUsersByUserId(UUID requestingUserId); + /** + * Retrieves all friends of a user as MinimalFriendDTO objects with only essential fields. + * This is optimized for friend selection lists (activity creation, activity types) to reduce memory usage. + * + * Fields included: id, username, name, profilePicture + * Fields excluded: bio, email, hasCompletedOnboarding, provider + * + * @param requestingUserId the unique identifier of the user requesting their friends + * @return List of MinimalFriendDTO objects representing the user's friends + * @throws com.danielagapov.spawn.Exceptions.Base.BaseNotFoundException if user doesn't exist + */ + List getMinimalFriendUsersByUserId(UUID requestingUserId); + /** * Retrieves all friends of a user as User entities. * diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSocialMediaService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSocialMediaService.java index 9a0372ed..89c8d32b 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSocialMediaService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserSocialMediaService.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.user.internal.services; -import com.danielagapov.spawn.user.api.dto.UpdateUserSocialMediaDTO; -import com.danielagapov.spawn.user.api.dto.UserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UpdateUserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserSocialMediaDTO; import java.util.UUID; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserStatsService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserStatsService.java index 385e27e8..3911bc56 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/IUserStatsService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/IUserStatsService.java @@ -1,6 +1,6 @@ package com.danielagapov.spawn.user.internal.services; -import com.danielagapov.spawn.user.api.dto.UserStatsDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserStatsDTO; import java.util.UUID; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserFriendshipQueryService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserFriendshipQueryService.java index 56609567..50480614 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserFriendshipQueryService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserFriendshipQueryService.java @@ -6,7 +6,8 @@ import com.danielagapov.spawn.shared.util.LoggingUtils; import com.danielagapov.spawn.shared.util.UserStatus; import com.danielagapov.spawn.social.internal.repositories.IFriendshipRepository; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; import com.danielagapov.spawn.user.internal.domain.User; import com.danielagapov.spawn.user.internal.repositories.IUserRepository; import org.springframework.beans.factory.annotation.Value; @@ -118,6 +119,35 @@ public List getFullFriendUsersByUserId(UUID requestingUserId) } } + @Override + public List getMinimalFriendUsersByUserId(UUID requestingUserId) { + try { + List friendIds = getFriendUserIdsByUserId(requestingUserId); + if (friendIds.isEmpty()) { + return List.of(); + } + List friendUsers = userRepository.findAllById(friendIds); + List result = new ArrayList<>(); + for (User friend : friendUsers) { + // Skip admin users + if (adminUsername.equals(friend.getUsername())) { + continue; + } + MinimalFriendDTO dto = new MinimalFriendDTO( + friend.getId(), + friend.getUsername(), + friend.getName(), + friend.getProfilePictureUrlString() + ); + result.add(dto); + } + return result; + } catch (Exception e) { + logger.error("Error retrieving minimal friend users: " + e.getMessage()); + throw e; + } + } + @Override public boolean isUserFriendOfUser(UUID userId, UUID potentialFriendId) { try { diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchEventListener.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchEventListener.java index 63795ec7..15482660 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchEventListener.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchEventListener.java @@ -3,7 +3,7 @@ import com.danielagapov.spawn.shared.events.UserSearchEvents.*; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.SearchedUserResult; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchQueryService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchQueryService.java index 6fc2ccc6..eae92b5f 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchQueryService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchQueryService.java @@ -3,7 +3,7 @@ import com.danielagapov.spawn.shared.events.UserSearchEvents.*; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.SearchedUserResult; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchService.java index c568e6f1..1e9b4c28 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSearchService.java @@ -3,8 +3,8 @@ import com.danielagapov.spawn.social.api.dto.CreateFriendRequestDTO; import com.danielagapov.spawn.social.api.dto.FetchFriendRequestDTO; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.user.api.dto.SearchResultUserDTO; import com.danielagapov.spawn.shared.util.ParticipationStatus; import com.danielagapov.spawn.shared.util.UserRelationshipType; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserService.java index adf47754..3f82a0d9 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserService.java @@ -1,6 +1,10 @@ package com.danielagapov.spawn.user.internal.services; import com.danielagapov.spawn.user.api.dto.*; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserProfileInfoDTO; import com.danielagapov.spawn.shared.util.EntityType; import com.danielagapov.spawn.shared.util.UserStatus; import com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException; @@ -384,6 +388,17 @@ public List getFullFriendUsersByUserId(UUID requestingUserId) return friendshipQueryService.getFullFriendUsersByUserId(requestingUserId); } + /** + * @param requestingUserId the user who's requesting this from the mobile app, + * typically from activity creation or activity type management views. + * @return `MinimalFriendDTO` list of friends for the requesting user, + * containing only essential fields (id, username, name, profilePicture) to reduce memory usage + */ + @Override + public List getMinimalFriendUsersByUserId(UUID requestingUserId) { + return friendshipQueryService.getMinimalFriendUsersByUserId(requestingUserId); + } + /** * Fallback method to get friends from the "Everyone" tag when the optimized query returns no results */ diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSocialMediaService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSocialMediaService.java index 0a6be94c..6777aa2a 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserSocialMediaService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserSocialMediaService.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.user.internal.services; -import com.danielagapov.spawn.user.api.dto.UpdateUserSocialMediaDTO; -import com.danielagapov.spawn.user.api.dto.UserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UpdateUserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserSocialMediaDTO; import com.danielagapov.spawn.user.internal.domain.User; import com.danielagapov.spawn.user.internal.domain.UserSocialMedia; import com.danielagapov.spawn.user.internal.repositories.IUserRepository; diff --git a/src/main/java/com/danielagapov/spawn/user/internal/services/UserStatsService.java b/src/main/java/com/danielagapov/spawn/user/internal/services/UserStatsService.java index 1e20fbd9..0eda8bbb 100644 --- a/src/main/java/com/danielagapov/spawn/user/internal/services/UserStatsService.java +++ b/src/main/java/com/danielagapov/spawn/user/internal/services/UserStatsService.java @@ -1,6 +1,6 @@ package com.danielagapov.spawn.user.internal.services; -import com.danielagapov.spawn.user.api.dto.UserStatsDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserStatsDTO; import com.danielagapov.spawn.shared.util.EntityType; import com.danielagapov.spawn.shared.util.ParticipationStatus; import com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException; diff --git a/src/test/java/com/danielagapov/spawn/ControllerTests/ActivityControllerTests.java b/src/test/java/com/danielagapov/spawn/ControllerTests/ActivityControllerTests.java index fb9ef388..30f27ec3 100644 --- a/src/test/java/com/danielagapov/spawn/ControllerTests/ActivityControllerTests.java +++ b/src/test/java/com/danielagapov/spawn/ControllerTests/ActivityControllerTests.java @@ -203,8 +203,9 @@ void createActivity_ShouldReturnCreated_WhenValidActivity() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(activityDTO))) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value(activityId.toString())) - .andExpect(jsonPath("$.title").value("Test Activity")); + .andExpect(jsonPath("$.activity.id").value(activityId.toString())) + .andExpect(jsonPath("$.activity.title").value("Test Activity")) + .andExpect(jsonPath("$.friendSuggestion").doesNotExist()); verify(activityService, times(1)).createActivityWithSuggestions(any(ActivityDTO.class)); } @@ -542,11 +543,12 @@ void createActivity_DirectCall_ShouldReturnCreated_WhenSuccessful() { when(activityService.createActivityWithSuggestions(any(ActivityDTO.class))) .thenReturn(fullFeedActivityDTO); - ResponseEntity response = activityController.createActivity(activityDTO); + ResponseEntity response = activityController.createActivity(activityDTO); assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertNotNull(response.getBody()); - assertEquals(activityId, response.getBody().getId()); + assertNotNull(response.getBody().getActivity()); + assertEquals(activityId, response.getBody().getActivity().getId()); verify(activityService, times(1)).createActivityWithSuggestions(any(ActivityDTO.class)); } diff --git a/src/test/java/com/danielagapov/spawn/ControllerTests/UserControllerTests.java b/src/test/java/com/danielagapov/spawn/ControllerTests/UserControllerTests.java index 9bbf6b9a..bb4c1bd9 100644 --- a/src/test/java/com/danielagapov/spawn/ControllerTests/UserControllerTests.java +++ b/src/test/java/com/danielagapov/spawn/ControllerTests/UserControllerTests.java @@ -2,7 +2,7 @@ import com.danielagapov.spawn.user.api.UserController; import com.danielagapov.spawn.user.api.dto.AbstractUserDTO; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; import com.danielagapov.spawn.shared.util.EntityType; import com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; diff --git a/src/test/java/com/danielagapov/spawn/IntegrationTests/FriendshipIntegrationTests.java b/src/test/java/com/danielagapov/spawn/IntegrationTests/FriendshipIntegrationTests.java index b6351741..5b940ce7 100644 --- a/src/test/java/com/danielagapov/spawn/IntegrationTests/FriendshipIntegrationTests.java +++ b/src/test/java/com/danielagapov/spawn/IntegrationTests/FriendshipIntegrationTests.java @@ -1,7 +1,7 @@ package com.danielagapov.spawn.IntegrationTests; import com.danielagapov.spawn.social.api.dto.CreateFriendRequestDTO; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; import com.danielagapov.spawn.shared.util.UserStatus; import com.danielagapov.spawn.social.internal.domain.Friendship; import com.danielagapov.spawn.social.internal.domain.FriendRequest; diff --git a/src/test/java/com/danielagapov/spawn/ServiceTests/ActivityTypeServiceTests.java b/src/test/java/com/danielagapov/spawn/ServiceTests/ActivityTypeServiceTests.java index 7fb87060..c36f11f2 100644 --- a/src/test/java/com/danielagapov/spawn/ServiceTests/ActivityTypeServiceTests.java +++ b/src/test/java/com/danielagapov/spawn/ServiceTests/ActivityTypeServiceTests.java @@ -999,10 +999,10 @@ void batchUpdate_ShouldHandleAssociatedFriends_WhenActivityTypeHasFriendsAssocia ActivityTypeDTO activityTypeWithFriends = new ActivityTypeDTO( activityTypeId1, "Chill", - Arrays.asList( - new com.danielagapov.spawn.user.api.dto.BaseUserDTO(friend1Id, "Friend One", "friend1@test.com", "friend1", "bio1", "pic1.jpg"), - new com.danielagapov.spawn.user.api.dto.BaseUserDTO(friend2Id, "Friend Two", "friend2@test.com", "friend2", "bio2", "pic2.jpg") - ), + Arrays.asList( + new com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO(friend1Id, "friend1", "Friend One", "pic1.jpg"), + new com.danielagapov.spawn.user.api.dto.FriendUser.MinimalFriendDTO(friend2Id, "friend2", "Friend Two", "pic2.jpg") + ), "🛋️", 1, userId, diff --git a/src/test/java/com/danielagapov/spawn/ServiceTests/CacheServiceTests.java b/src/test/java/com/danielagapov/spawn/ServiceTests/CacheServiceTests.java index e1a53987..b050ba13 100644 --- a/src/test/java/com/danielagapov/spawn/ServiceTests/CacheServiceTests.java +++ b/src/test/java/com/danielagapov/spawn/ServiceTests/CacheServiceTests.java @@ -15,8 +15,8 @@ import com.danielagapov.spawn.user.internal.services.IUserSocialMediaService; import com.danielagapov.spawn.user.internal.services.IUserStatsService; import com.danielagapov.spawn.user.internal.services.IRecentlySpawnedService; -import com.danielagapov.spawn.user.api.dto.UserSocialMediaDTO; -import com.danielagapov.spawn.user.api.dto.UserStatsDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserSocialMediaDTO; +import com.danielagapov.spawn.user.api.dto.Profile.UserStatsDTO; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/danielagapov/spawn/ServiceTests/UserSearchServiceTests.java b/src/test/java/com/danielagapov/spawn/ServiceTests/UserSearchServiceTests.java index 14e4bccd..9bfeb15c 100644 --- a/src/test/java/com/danielagapov/spawn/ServiceTests/UserSearchServiceTests.java +++ b/src/test/java/com/danielagapov/spawn/ServiceTests/UserSearchServiceTests.java @@ -3,8 +3,8 @@ import com.danielagapov.spawn.social.api.dto.FetchFriendRequestDTO; import com.danielagapov.spawn.user.api.dto.BaseUserDTO; -import com.danielagapov.spawn.user.api.dto.FullFriendUserDTO; -import com.danielagapov.spawn.user.api.dto.RecommendedFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.FullFriendUserDTO; +import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO; import com.danielagapov.spawn.shared.util.UserRelationshipType; import com.danielagapov.spawn.shared.util.UserStatus; import com.danielagapov.spawn.shared.exceptions.Logger.ILogger; diff --git a/src/test/java/com/danielagapov/spawn/UtilityTests/UserMapperTests.java b/src/test/java/com/danielagapov/spawn/UtilityTests/UserMapperTests.java index 7e87cbdc..96fcd5c0 100644 --- a/src/test/java/com/danielagapov/spawn/UtilityTests/UserMapperTests.java +++ b/src/test/java/com/danielagapov/spawn/UtilityTests/UserMapperTests.java @@ -337,7 +337,7 @@ void shouldMapDTOListToEntityList() { List dtos = List.of(dto1, dto2); // When - List users = UserMapper.toEntityList(dtos); + List users = UserMapper.toEntityListFromBaseUserDTOs(dtos); // Then assertThat(users).hasSize(2);