Changeset 2b08bed
- Timestamp:
- 01/30/26 22:14:36 (5 months ago)
- Branches:
- main
- Children:
- 2730163
- Parents:
- 98992cf
- Files:
-
- 5 added
- 14 edited
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AlbumController.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/NonAdminUserController.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/AlbumDto.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/MusicalEntityDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/NonAdminUserDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/FollowRepository.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/LikeRepository.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/SongRepository.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AlbumService.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/FollowService.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/NonAdminUserService.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/mappers/NonAdminUserMapper.java (modified) (1 diff)
-
frontend/src/App.tsx (modified) (1 diff)
-
frontend/src/components/userProfile/ArtistView.tsx (modified) (1 diff)
-
frontend/src/components/userProfile/ListenerView.tsx (modified) (1 diff)
-
frontend/src/components/userProfile/UserListModal.tsx (added)
-
frontend/src/pages/MusicalCollection.tsx (added)
-
frontend/src/pages/UserDetailView.tsx (modified) (8 diffs)
-
frontend/src/utils/types.ts (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/NonAdminUserController.java
r98992cf r2b08bed 35 35 } 36 36 37 @GetMapping("/followers/{id}") 38 public HttpEntity<List<NonAdminUserDto>>getFollowersForUser(@PathVariable Long id){ 39 return ResponseEntity.ok(followService.getFollowersForUser(id)); 40 } 41 42 @GetMapping("/following/{id}") 43 public HttpEntity<List<NonAdminUserDto>>getFollowingForUser(@PathVariable Long id){ 44 return ResponseEntity.ok(followService.getFollowingForUser(id)); 45 } 46 37 47 @PostMapping("/follow/{id}") 38 48 public HttpEntity<NonAdminUserDto>followUser(@PathVariable Long id){ … … 41 51 return ResponseEntity.ok(nonAdminUserService.getById(id)); 42 52 } 53 54 43 55 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/MusicalEntityDto.java
r98992cf r2b08bed 13 13 private String genre; 14 14 private String type; 15 private String releasedBy; 16 private Boolean isLikedByCurrentUser; 15 17 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/NonAdminUserDto.java
r98992cf r2b08bed 12 12 private String username; 13 13 private String fullName; 14 private String profilePhoto; 14 15 private String userType; 15 16 private Long followers; -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/FollowRepository.java
r98992cf r2b08bed 8 8 import com.ukim.finki.develop.finkwave.model.Follow; 9 9 import com.ukim.finki.develop.finkwave.model.FollowId; 10 11 import java.util.List; 10 12 11 13 @Repository … … 23 25 boolean isFollowing(@Param("currentUserId") Long currentUSerId,@Param("selectedUserId")Long selectedUserId); 24 26 27 List<Follow>getFollowsByFollowee_Id(Long id); 28 29 List<Follow>getFollowsByFollower_Id(Long id); 30 25 31 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/LikeRepository.java
r98992cf r2b08bed 1 1 package com.ukim.finki.develop.finkwave.repository; 2 2 3 import com.ukim.finki.develop.finkwave.model.MusicalEntity; 3 4 import com.ukim.finki.develop.finkwave.model.dto.MusicalEntityDto; 4 5 import org.springframework.data.jpa.repository.JpaRepository; … … 19 20 "CASE WHEN s.id IS NOT NULL THEN 'SONG' " + 20 21 "WHEN a.id IS NOT NULL THEN 'ALBUM' " + 21 "ELSE 'UNKNOWN' END)" + 22 "ELSE 'UNKNOWN' END," + 23 "u.fullName," + 24 "true)" + 22 25 "FROM Like l " + 23 26 "LEFT JOIN Song s ON s.musicalEntities.id = l.musicalEntity.id " + 24 27 "LEFT JOIN Album a ON a.musicalEntities.id = l.musicalEntity.id " + 28 "LEFT JOIN User u ON u.id=l.musicalEntity.releasedBy.id "+ 25 29 "WHERE l.listener.id = :listenerId") 26 30 List<MusicalEntityDto> findLikedEntitiesWithTypeByListenerId(@Param("listenerId") Long listenerId); 31 32 33 @Query("SELECT CASE WHEN COUNT (l)>0 THEN true ELSE false END " + 34 "FROM MusicalEntity me " + 35 "JOIN Like l on l.musicalEntity.id=me.id " + 36 "WHERE l.listener.id=:userId" 37 38 ) 39 boolean isLikedByUser(@Param("userId") Long userId); 27 40 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/SongRepository.java
r98992cf r2b08bed 1 1 package com.ukim.finki.develop.finkwave.repository; 2 2 3 import com.ukim.finki.develop.finkwave.model.dto.MusicalEntityDto; 3 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 4 7 import org.springframework.stereotype.Repository; 5 8 … … 11 14 public interface SongRepository extends JpaRepository<Song, Long> { 12 15 13 List<Song>findAllByIdIn(List<Long>ids); 16 17 @Query("SELECT NEW com.ukim.finki.develop.finkwave.model.dto.MusicalEntityDto( "+ 18 "s.id, " + 19 "me.title, " + 20 "me.genre, " + 21 "'SONG'," + 22 "u.fullName, " + 23 "(CASE WHEN l.id IS NOT NULL THEN true ELSE false END)) "+ 24 "FROM Song s "+ 25 "JOIN MusicalEntity me on me.id=s.musicalEntities.id "+ 26 "JOIN User u on u.id=me.releasedBy.id "+ 27 "LEFT JOIN Like l on l.musicalEntity.id=s.id AND l.listener.id=:currentUserId "+ 28 "WHERE s.album.id=:albumId" 29 30 ) 31 List<MusicalEntityDto>findSongsByAlbum(@Param("albumId") Long albumId, @Param("currentUserId")Long currentUserId); 14 32 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/FollowService.java
r98992cf r2b08bed 6 6 import com.ukim.finki.develop.finkwave.model.FollowId; 7 7 import com.ukim.finki.develop.finkwave.model.NonAdminUser; 8 import com.ukim.finki.develop.finkwave.model.dto.NonAdminUserDto; 9 import com.ukim.finki.develop.finkwave.repository.ArtistRepository; 8 10 import com.ukim.finki.develop.finkwave.repository.FollowRepository; 9 11 import com.ukim.finki.develop.finkwave.repository.NonAdminUserRepository; 12 import com.ukim.finki.develop.finkwave.service.mappers.NonAdminUserMapper; 10 13 import lombok.AllArgsConstructor; 11 14 import org.springframework.stereotype.Service; 15 16 import java.util.ArrayList; 17 import java.util.List; 18 import java.util.stream.Collectors; 12 19 13 20 @Service … … 18 25 private final NonAdminUserRepository nonAdminUserRepository; 19 26 private final AuthService authService; 27 private final ArtistRepository artistRepository; 28 private final NonAdminUserMapper mapper; 29 30 31 32 public List<NonAdminUserDto>getFollowersForUser(Long id){ 33 if (artistRepository.existsById(id)){ 34 throw new FollowException("Cannot view for artist"); 35 } 36 List<Follow>followers=followRepository.getFollowsByFollowee_Id(id); 37 Long currentUserId=authService.getCurrentUserID(); 38 return followers.stream() 39 .map(f->{ 40 NonAdminUserDto dto=mapper.toDto(f.getFollower(),null,null,null); 41 dto.setIsFollowedByCurrentUser(followRepository.isFollowing(currentUserId,f.getFollower().getId())); 42 return dto; 43 }) 44 .toList(); 45 } 46 47 public List<NonAdminUserDto>getFollowingForUser(Long id){ 48 if (artistRepository.existsById(id)){ 49 throw new FollowException("Cannot view for artist"); 50 } 51 List<Follow>followers=followRepository.getFollowsByFollower_Id(id); 52 Long currentUserId=authService.getCurrentUserID(); 53 return followers.stream() 54 .map(f->{ 55 NonAdminUserDto dto=mapper.toDto(f.getFollowee(),null,null,null); 56 dto.setIsFollowedByCurrentUser(followRepository.isFollowing(currentUserId,f.getFollowee().getId())); 57 return dto; 58 }) 59 .toList(); 60 } 61 20 62 21 63 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/NonAdminUserService.java
r98992cf r2b08bed 29 29 .map(user -> { 30 30 String type = determineType(user.getId()); 31 Long followers = followRepository.countByFolloweeId(user.getId()); 32 Long following = followRepository.countByFollowerId(user.getId()); 33 return mapper.toDto(user, type, followers, following); 31 return mapper.toDto(user, type, null, null); 34 32 }) 35 33 .collect(Collectors.toList()); -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/mappers/NonAdminUserMapper.java
r98992cf r2b08bed 36 36 } 37 37 38 39 38 40 private void populateBaseClassFields(NonAdminUserDto dto,NonAdminUser user,String type,Long followers,Long following){ 41 39 42 dto.setId(user.getId()); 40 43 dto.setUsername(user.getUser().getUsername()); 41 44 dto.setFullName(user.getUser().getFullName()); 45 dto.setProfilePhoto(user.getUser().getProfilePhoto()); 42 46 dto.setUserType(type); 43 47 dto.setFollowers(followers); -
frontend/src/App.tsx
r98992cf r2b08bed 8 8 import Register from "./pages/Register"; 9 9 import UserDetailView from "./pages/UserDetailView"; 10 import MusicalCollection from "./pages/MusicalCollection"; 10 11 11 12 const Layout = () => { 12 return (13 <div className="flex flex-col min-h-screen">14 <Nav />15 <ToastContainer16 position="top-right"17 autoClose={2000}18 hideProgressBar={false}19 newestOnTop={false}20 closeOnClick21 rtl={false}22 pauseOnFocusLoss23 draggable24 pauseOnHover25 theme="light"26 />27 <main className="grow">28 <Outlet />29 </main>30 </div>31 );13 return ( 14 <div className="flex flex-col min-h-screen"> 15 <Nav /> 16 <ToastContainer 17 position="top-right" 18 autoClose={2000} 19 hideProgressBar={false} 20 newestOnTop={false} 21 closeOnClick 22 rtl={false} 23 pauseOnFocusLoss 24 draggable 25 pauseOnHover 26 theme="light" 27 /> 28 <main className="grow"> 29 <Outlet /> 30 </main> 31 </div> 32 ); 32 33 }; 33 34 34 35 const router = createBrowserRouter([ 35 { 36 path: "", 37 element: <Layout />, 38 children: [ 39 { 40 path: "/", 41 element: <LandingPage />, 42 }, 43 { 44 path: "/login", 45 element: <Login />, 46 }, 47 { 48 path: "/users", 49 element: <AllUsers />, 50 }, 51 { 52 path: "/users/:userId", 53 element: <UserDetailView />, 54 }, 55 { 56 path: "/register", 57 element: <Register />, 58 }, 59 ], 60 }, 36 { 37 path: "", 38 element: <Layout />, 39 children: [ 40 { 41 path: "/", 42 element: <LandingPage />, 43 }, 44 { 45 path: "/login", 46 element: <Login />, 47 }, 48 { 49 path: "/users", 50 element: <AllUsers />, 51 }, 52 { 53 path: "/users/:userId", 54 element: <UserDetailView />, 55 }, 56 { 57 path: "/register", 58 element: <Register />, 59 }, 60 { 61 path: "/collection/:type/:id", 62 element: <MusicalCollection />, 63 }, 64 ], 65 }, 61 66 ]); 62 67 63 68 function App() { 64 return <RouterProvider router={router} />;69 return <RouterProvider router={router} />; 65 70 } 66 71 -
frontend/src/components/userProfile/ArtistView.tsx
r98992cf r2b08bed 37 37 className="group cursor-pointer" 38 38 onClick={() => 39 navigate(`/ musical-entity/${album.musicalEntityId}`)39 navigate(`/collection/album/${album.musicalEntityId}`) 40 40 } 41 41 > -
frontend/src/components/userProfile/ListenerView.tsx
r98992cf r2b08bed 31 31 key={playlist.id} 32 32 className="group cursor-pointer" 33 onClick={() => navigate(`/playlist /${playlist.id}`)}33 onClick={() => navigate(`/playlists/${playlist.id}`)} 34 34 > 35 35 <div className="aspect-square rounded-md overflow-hidden bg-gray-100 mb-2 relative shadow-sm group-hover:shadow-lg transition-all"> -
frontend/src/pages/UserDetailView.tsx
r98992cf r2b08bed 4 4 import ArtistView from "../components/userProfile/ArtistView"; 5 5 import ListenerView from "../components/userProfile/ListenerView"; 6 import UserListModal from "../components/userProfile/UserListModal"; 6 7 import type { 7 8 MusicalEntity, 8 9 Playlist, 9 10 ArtistContribution, 11 BaseNonAdminUser, 10 12 } from "../utils/types"; 11 13 12 interface BaseUser { 13 id: number; 14 fullName: string; 15 userType: string; 16 followers: number; 17 following: number; 18 isFollowedByCurrentUser: boolean; 19 } 20 21 interface Artist extends BaseUser { 14 interface Artist extends BaseNonAdminUser { 22 15 userType: "ARTIST"; 23 16 contributions: ArtistContribution[]; 24 17 } 25 interface Listener extends Base User {18 interface Listener extends BaseNonAdminUser { 26 19 userType: "LISTENER"; 27 20 likedEntities: MusicalEntity[]; … … 33 26 const UserDetail = () => { 34 27 // user refers to the selected user NOT to the user from context 28 const baseURL = import.meta.env.VITE_API_BASE_URL; 35 29 const { userId } = useParams(); 36 30 const navigate = useNavigate(); 37 31 const [user, setUser] = useState<UserProfile | null>(null); 38 32 const [error, setError] = useState<string | null>(null); 33 const [showModal, setShowModal] = useState(false); 34 const [modalTitle, setModalTitle] = useState(""); 35 const [modalUsers, setModalUsers] = useState<any[]>([]); 36 const [isLoadingModal, setIsLoadingModal] = useState(false); 39 37 const [isFollowing, setIsFollowing] = useState(false); 40 38 … … 47 45 `/users/follow/${userId}`, 48 46 ); 47 setUser(response.data); 49 48 } catch (err: any) { 50 49 console.error(err.response?.data?.error); 51 50 } finally { 52 51 setIsFollowing(false); 52 } 53 }; 54 55 const displayFollowers = async () => { 56 setIsLoadingModal(true); 57 try { 58 const response = await axiosInstance.get(`/users/followers/${userId}`); 59 setModalUsers(response.data); 60 setModalTitle("Followers"); 61 setShowModal(true); 62 } catch (err) { 63 console.error("Failed to fetch followers"); 64 } finally { 65 setIsLoadingModal(false); 66 } 67 }; 68 const displayFollowing = async () => { 69 setIsLoadingModal(true); 70 try { 71 const response = await axiosInstance.get(`/users/following/${userId}`); 72 setModalUsers(response.data); 73 setModalTitle("Following"); 74 setShowModal(true); 75 } catch (err) { 76 console.error("Failed to fetch following users"); 77 } finally { 78 setIsLoadingModal(false); 79 } 80 }; 81 82 const handleFollowInModal = async (targetId: number) => { 83 try { 84 await axiosInstance.post(`/users/follow/${targetId}`); 85 setModalUsers((prevUsers) => 86 prevUsers.map((u) => { 87 if (u.id === targetId) { 88 const isNowFollowing = !u.isFollowedByCurrentUser; 89 return { 90 ...u, 91 isFollowedByCurrentUser: isNowFollowing, 92 }; 93 } 94 return u; 95 }), 96 ); 97 98 if (user && user.id === targetId) { 99 const response = await axiosInstance.get(`/users/${targetId}`); 100 setUser(response.data); 101 } 102 } catch (err) { 103 console.error("Failed to toggle follow in modal", err); 53 104 } 54 105 }; … … 60 111 const response = await axiosInstance.get(`/users/${userId}`); 61 112 console.log(response.data); 62 63 113 setUser(response.data); 64 114 } catch (err: any) { … … 84 134 return ( 85 135 <div className="container mx-auto p-6"> 136 {isLoadingModal && ( 137 <div className="fixed inset-0 z-40 bg-black/30 backdrop-blur-sm flex items-center justify-center"> 138 <div className="flex items-center gap-3"> 139 <div className="w-6 h-6 border-3 border-blue-500 border-t-transparent rounded-full animate-spin"></div> 140 </div> 141 </div> 142 )} 86 143 <button 87 144 onClick={() => navigate(-1)} … … 94 151 <div className="flex items-start gap-6 mb-8"> 95 152 <div className="shrink-0"> 96 <div className="w-32 h-32 rounded-full bg-linear-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white text-4xl font-bold shadow-lg"> 97 {user.fullName.charAt(0).toUpperCase()} 153 <div className="w-32 h-32 rounded-full bg-linear-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white text-4xl font-bold shadow-lg overflow-hidden"> 154 {user.profilePhoto ? ( 155 <img 156 src={`${baseURL}/${user.profilePhoto}`} 157 alt={user.fullName} 158 className="w-full h-full object-cover" 159 /> 160 ) : ( 161 user.fullName.charAt(0).toUpperCase() 162 )} 98 163 </div> 99 164 </div> … … 106 171 107 172 <div className="flex gap-6 mb-4 text-gray-700"> 108 <div className="flex flex-col"> 173 <div 174 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`} 175 onClick={ 176 user.userType === "LISTENER" ? displayFollowers : undefined 177 } 178 > 109 179 <span className="text-2xl font-bold">{user.followers}</span> 110 180 <span className="text-sm text-gray-500">Followers</span> 111 181 </div> 112 <div className="flex flex-col"> 182 <div 183 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`} 184 onClick={ 185 user.userType === "LISTENER" ? displayFollowing : undefined 186 } 187 > 113 188 <span className="text-2xl font-bold">{user.following}</span> 114 189 <span className="text-sm text-gray-500">Following</span> … … 144 219 /> 145 220 )} 221 222 {showModal && ( 223 <UserListModal 224 title={modalTitle} 225 users={modalUsers} 226 onClose={() => setShowModal(false)} 227 onFollowToggle={handleFollowInModal} 228 /> 229 )} 146 230 </div> 147 231 </div> -
frontend/src/utils/types.ts
r98992cf r2b08bed 19 19 genre: string; 20 20 type: string; 21 releasedBy: string; 21 22 } 22 23 … … 27 28 creatorName: string; 28 29 } 30 31 export interface BaseNonAdminUser { 32 id: number; 33 fullName: string; 34 userType: string; 35 profilePhoto: string; 36 followers: number; 37 following: number; 38 isFollowedByCurrentUser: boolean; 39 }
Note:
See TracChangeset
for help on using the changeset viewer.
