ProfileService.java
package com.deusto.deuspotify.services;
import com.deusto.deuspotify.model.Profile;
import com.deusto.deuspotify.model.Song;
import com.deusto.deuspotify.repositories.ProfileRepository;
import com.deusto.deuspotify.repositories.SongRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.context.annotation.Lazy;
import com.deusto.deuspotify.security.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.Optional;
/**
* @class ProfileService
* @brief Service for managing user profiles, authentication and user details for Spring Security.
*
* This service provides methods for registering, retrieving, updating, and deleting user profiles.
* It also supports authentication via Spring Security and token-based user identification using JWT.
*/
@Service
public class ProfileService implements UserDetailsService {
private final ProfileRepository profileRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
/**
* Constructor for ProfileService.
*
* @param profileRepository Repository to manage Profile entities.
* @param passwordEncoder Password encoder for secure password storage.
*/
@Autowired
private SongRepository songRepository;
public ProfileService(ProfileRepository profileRepository, @Lazy PasswordEncoder passwordEncoder) {
this.profileRepository = profileRepository;
this.passwordEncoder = passwordEncoder;
}
/**
* @brief Load a user by their username for Spring Security.
* @param username Username of the user.
* @return UserDetails object containing user information.
* @throws UsernameNotFoundException if the user is not found.
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Profile profile = profileRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User
.withUsername(profile.getUsername())
.password(profile.getPassword())
.roles(profile.isAdmin() ? "ADMIN" : "USER")
.build();
}
/**
* @brief Retrieve all user profiles.
* @return List of all Profile entities.
*/
public List<Profile> getAllProfiles() {
return profileRepository.findAll();
}
/**
* @brief Retrieve a profile by its ID.
* @param id ID of the profile.
* @return Optional containing the Profile if found.
*/
public Optional<Profile> getProfileById(Long id) {
return profileRepository.findById(id);
}
/**
* @brief Register a new user with encoded password.
* @param profile Profile object containing registration data.
* @return The saved Profile object.
* @throws RuntimeException if the username already exists.
*/
@Transactional
public Profile registerUser(Profile profile) {
if (profileRepository.findByUsername(profile.getUsername()).isPresent()) {
throw new RuntimeException("Username already exists.");
}
profile.setPassword(passwordEncoder.encode(profile.getPassword()));
return profileRepository.save(profile);
}
/**
* @brief Update an existing user profile.
* @param id ID of the profile to update.
* @param updatedProfile New profile data.
* @return Optional containing the updated Profile.
*/
@Transactional
public Optional<Profile> updateProfile(Long id, Profile updatedProfile) {
return profileRepository.findById(id).map(profile -> {
profile.setUsername(updatedProfile.getUsername());
if (!updatedProfile.getPassword().isEmpty()) {
profile.setPassword(passwordEncoder.encode(updatedProfile.getPassword()));
}
profile.setEmail(updatedProfile.getEmail());
profile.setFriendsList(updatedProfile.getFriendsList());
profile.setFavouriteSongs(updatedProfile.getFavouriteSongs());
profile.setPlaylists(updatedProfile.getPlaylists());
profile.setAdmin(updatedProfile.isAdmin());
return profileRepository.save(profile);
});
}
/**
* @brief Delete a user profile.
* @param id ID of the profile to delete.
* @return true if the profile was deleted, false otherwise.
*/
@Transactional
public boolean deleteProfile(Long id) {
if (profileRepository.existsById(id)) {
profileRepository.deleteById(id);
return true;
}
return false;
}
/**
* @brief Validate user login by comparing raw and encoded passwords.
* @param username The username.
* @param password The raw password.
* @return Optional containing the Profile if login is successful.
*/
public Optional<Profile> login(String username, String password) {
return profileRepository.findByUsername(username)
.filter(profile -> passwordEncoder.matches(password, profile.getPassword()));
}
/**
* @brief Extract and return the currently authenticated user based on the JWT token.
* @param request The HTTP request containing the Authorization header.
* @return The authenticated Profile.
* @throws RuntimeException if no token is found or the user does not exist.
*/
public Profile getAuthenticatedUser(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("No JWT token found");
}
String token = authHeader.substring(7);
String username = jwtUtil.extractUsername(token);
return profileRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
}
/**
* @brief Retrieve a profile by its username.
* @param username The username to search for.
* @return Optional containing the Profile if found.
*/
public Optional<Profile> getProfileByUsername(String username) {
return profileRepository.findByUsername(username);
}
/**
* @brief Add the specified song to the user's favourites.
* @param username The username of the user.
* @param songId The ID of the song to add.
*/
@Transactional
public void addFavouriteSong(String username, Long songId) {
Profile profile = profileRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
Song song = songRepository.findById(songId)
.orElseThrow(() -> new RuntimeException("Song not found: " + songId));
profile.getFavouriteSongs().add(song);
profileRepository.save(profile);
}
/**
* @brief Remove the specified song from the user's favourites.
* @param username The username of the user.
* @param songId The ID of the song to remove.
*/
@Transactional
public void removeFavouriteSong(String username, Long songId) {
Profile profile = profileRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
Song song = songRepository.findById(songId)
.orElseThrow(() -> new RuntimeException("Song not found: " + songId));
profile.getFavouriteSongs().remove(song);
profileRepository.save(profile);
}
}