Changeset db5fb23
- Timestamp:
- 02/12/26 11:27:23 (5 months ago)
- Branches:
- main
- Children:
- d85e8e3
- Parents:
- 92db381
- Files:
-
- 4 added
- 10 edited
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/MusicalEntityController.java (modified) (3 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/MusicalEntity.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Song.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/AlbumSongsDto.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ArtistContributionSummaryDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishAlbumRequestDto.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishSongRequestDto.java (added)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/UserSearchResultDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ArtistService.java (modified) (3 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/MusicalEntityService.java (added)
-
frontend/src/pages/PublishSong.tsx (modified) (16 diffs)
-
frontend/src/utils/types.ts (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/MusicalEntityController.java
r92db381 rdb5fb23 2 2 3 3 import com.ukim.finki.develop.finkwave.model.dto.LikeStatusDto; 4 import com.ukim.finki.develop.finkwave.model.dto.PublishAlbumRequestDto; 5 import com.ukim.finki.develop.finkwave.model.dto.PublishSongRequestDto; 4 6 import com.ukim.finki.develop.finkwave.service.LikeService; 7 import com.ukim.finki.develop.finkwave.service.MusicalEntityService; 5 8 import lombok.AllArgsConstructor; 6 9 import org.springframework.http.HttpEntity; 10 import org.springframework.http.HttpStatus; 11 import org.springframework.http.MediaType; 7 12 import org.springframework.http.ResponseEntity; 8 import org.springframework.web.bind.annotation.PathVariable; 9 import org.springframework.web.bind.annotation.PostMapping; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.RestController; 13 import org.springframework.web.bind.annotation.*; 14 15 import java.io.IOException; 12 16 13 17 @RestController … … 17 21 18 22 private final LikeService likeService; 23 private final MusicalEntityService musicalEntityService; 19 24 20 25 @PostMapping("/{id}/like") … … 22 27 return ResponseEntity.ok(likeService.toggleLike(entityId)); 23 28 } 29 30 @PostMapping(value = "/publish/song", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) 31 public HttpEntity<?> publishSong(@ModelAttribute PublishSongRequestDto publishSongRequestDto) throws IOException { 32 musicalEntityService.handleSongPublish(publishSongRequestDto); 33 return ResponseEntity.status(HttpStatus.CREATED).build(); 34 } 35 36 @PostMapping(value = "/publish/album", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) 37 public HttpEntity<?> publishAlbum(@ModelAttribute PublishAlbumRequestDto publishAlbumRequestDto) throws IOException { 38 musicalEntityService.handleAlbumPublish(publishAlbumRequestDto); 39 return ResponseEntity.status(HttpStatus.CREATED).build(); 40 } 24 41 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/MusicalEntity.java
r92db381 rdb5fb23 3 3 import java.time.LocalDate; 4 4 5 import jakarta.persistence.*; 6 import lombok.*; 5 7 import org.hibernate.annotations.OnDelete; 6 8 import org.hibernate.annotations.OnDeleteAction; 7 9 8 10 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 9 10 import jakarta.persistence.Column;11 import jakarta.persistence.Entity;12 import jakarta.persistence.FetchType;13 import jakarta.persistence.Id;14 import jakarta.persistence.JoinColumn;15 import jakarta.persistence.ManyToOne;16 import jakarta.persistence.Table;17 import lombok.Getter;18 import lombok.Setter;19 11 20 12 @Getter … … 23 15 @Table(name = "musical_entities", schema = "project") 24 16 @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 17 @Builder 18 @NoArgsConstructor 19 @AllArgsConstructor 25 20 public class MusicalEntity { 26 21 @Id 27 22 @Column(name = "id", nullable = false) 23 @GeneratedValue(strategy = GenerationType.IDENTITY) 28 24 private Long id; 29 25 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Song.java
r92db381 rdb5fb23 15 15 @Id 16 16 @Column(name = "id", nullable = false) 17 @GeneratedValue(strategy = GenerationType.IDENTITY) 17 18 private Long id; 18 19 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ArtistContributionSummaryDto.java
r92db381 rdb5fb23 3 3 import lombok.AllArgsConstructor; 4 4 import lombok.Getter; 5 import lombok.NoArgsConstructor; 6 import lombok.Setter; 5 7 6 8 // this dto is useful when we already have all the necessary info for the song and just need the contribution info 7 9 @Getter 10 @Setter 11 @NoArgsConstructor 8 12 @AllArgsConstructor 9 13 public class ArtistContributionSummaryDto { 14 Long id = null; 10 15 private String artistName; 11 16 private String role; 17 18 public ArtistContributionSummaryDto(String artistName, String role) { 19 this.artistName = artistName; 20 this.role = role; 21 } 12 22 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/UserSearchResultDto.java
r92db381 rdb5fb23 3 3 4 4 public record UserSearchResultDto( 5 String fullName, 6 String username, 7 String profilePhoto 5 Long id, 6 String fullName, 7 String username, 8 String profilePhoto 8 9 ){} -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java
r92db381 rdb5fb23 17 17 18 18 @Query(value = """ 19 SELECT u. full_name, u.username, u.profile_photo from users u19 SELECT u.user_id, u.full_name, u.username, u.profile_photo from users u 20 20 WHERE (u.full_name ILIKE '%' || :searchTerm || '%' or u.username ILIKE '%' || :searchTerm || '%') 21 21 and u.listener = true and u.artist = false … … 25 25 26 26 @Query(value = """ 27 SELECT u. full_name, u.username, u.profile_photo from users u27 SELECT u.user_id, u.full_name, u.username, u.profile_photo from users u 28 28 WHERE (u.full_name ILIKE '%' || :searchTerm || '%' or u.username ILIKE '%' || :searchTerm || '%') and u.artist = true 29 29 LIMIT :limit -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ArtistService.java
r92db381 rdb5fb23 1 1 package com.ukim.finki.develop.finkwave.service; 2 2 3 import com.ukim.finki.develop.finkwave.exceptions.UserNotFoundException; 4 import com.ukim.finki.develop.finkwave.model.Artist; 3 5 import com.ukim.finki.develop.finkwave.model.dto.ArtistContributionDto; 4 6 import com.ukim.finki.develop.finkwave.repository.ArtistContributionRepository; 5 7 8 import com.ukim.finki.develop.finkwave.repository.ArtistRepository; 6 9 import org.springframework.transaction.annotation.Transactional; 7 10 import lombok.AllArgsConstructor; … … 14 17 public class ArtistService { 15 18 private final ArtistContributionRepository artistContributionRepository; 19 private final ArtistRepository artistRepository; 16 20 17 21 … … 21 25 } 22 26 27 public Artist getArtistById(Long id){ 28 return artistRepository.findById(id).orElseThrow(UserNotFoundException::new); 29 } 30 23 31 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java
r92db381 rdb5fb23 134 134 } 135 135 136 private User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException { 136 @Transactional 137 public User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException { 137 138 User.UserBuilder userBuilder = User.builder() 138 139 .username(authRequestDto.username()) -
frontend/src/pages/PublishSong.tsx
r92db381 rdb5fb23 23 23 const [genre, setGenre] = useState(""); 24 24 const [link, setLink] = useState(""); 25 const [ _coverFile, setCoverFile] = useState<File | null>(null); // TODO: Use in API call25 const [coverFile, setCoverFile] = useState<File | null>(null); 26 26 const [coverPreview, setCoverPreview] = useState<string | null>(null); 27 27 const [contributors, setContributors] = useState<Contributor[]>([]); … … 93 93 `/users/search?type=ARTIST&q=${encodeURIComponent(query)}&limit=5`, 94 94 ); 95 96 95 const filtered = response.data.filter( 97 96 (artist: ArtistSearchResult) => … … 129 128 } 130 129 131 if (contributors.some((c) => c. username === artist.username)) {130 if (contributors.some((c) => c.id === artist.id)) { 132 131 toast.error("This contributor is already added"); 133 132 return; … … 137 136 ...contributors, 138 137 { 139 username: artist.username,138 id: artist.id, 140 139 fullName: artist.fullName, 141 140 role: selectedContributorRole, … … 148 147 149 148 // Remove contributor 150 const handleRemoveContributor = ( username: string) => {151 setContributors(contributors.filter((c) => c. username !== username));149 const handleRemoveContributor = (id: number) => { 150 setContributors(contributors.filter((c) => c.id !== id)); 152 151 }; 153 152 … … 242 241 } 243 242 244 if (song.contributors.some((c) => c. username === artist.username)) {243 if (song.contributors.some((c) => c.id === artist.id)) { 245 244 toast.error("This contributor is already added"); 246 245 return; … … 249 248 const role = albumSongSelectedRole[songIndex] || ROLE_OPTIONS[0].value; 250 249 const newContributor: Contributor = { 251 username: artist.username,250 id: artist.id, 252 251 fullName: artist.fullName, 253 252 role, … … 263 262 }; 264 263 265 const handleRemoveAlbumSongContributor = ( 266 songIndex: number, 267 username: string, 268 ) => { 264 const handleRemoveAlbumSongContributor = (songIndex: number, id: number) => { 269 265 const song = albumSongs[songIndex]; 270 266 handleAlbumSongChange( 271 267 songIndex, 272 268 "contributors", 273 song.contributors.filter((c) => c. username !== username),269 song.contributors.filter((c) => c.id !== id), 274 270 ); 275 271 }; 276 272 277 // Form submission278 273 const handleSubmit = async (e: React.FormEvent) => { 279 274 e.preventDefault(); 280 275 281 // Validation282 276 if (!title.trim()) { 283 277 toast.error("Please enter a title"); … … 295 289 } 296 290 } else { 297 // Album validation298 291 for (let i = 0; i < albumSongs.length; i++) { 299 292 if (!albumSongs[i].title.trim()) { … … 311 304 312 305 try { 313 // TODO: replace with API call 314 // const formData = new FormData(); 315 // formData.append('title', title); 316 // formData.append('genre', genre); 317 // if (coverFile) formData.append('cover', coverFile); 318 // ... 319 320 // Simulate API call 321 await new Promise((resolve) => setTimeout(resolve, 1000)); 306 const formData = new FormData(); 307 formData.append("title", title); 308 formData.append("genre", genre); 309 if (coverFile) formData.append("cover", coverFile); 310 311 // Helper to append contributors in indexed format for Spring @ModelAttribute 312 const appendContributors = ( 313 formData: FormData, 314 contributors: Contributor[], 315 prefix: string, 316 ) => { 317 contributors.forEach((c, i) => { 318 formData.append(`${prefix}[${i}].id`, c.id.toString()); 319 formData.append(`${prefix}[${i}].artistName`, c.fullName); 320 formData.append(`${prefix}[${i}].role`, c.role); 321 }); 322 }; 323 324 if (releaseType === "album") { 325 // Append album songs in indexed format 326 albumSongs.forEach((song, songIndex) => { 327 formData.append(`albumSongs[${songIndex}].title`, song.title); 328 formData.append(`albumSongs[${songIndex}].link`, song.link); 329 // Append nested contributors for each song 330 song.contributors.forEach((c, contribIndex) => { 331 formData.append( 332 `albumSongs[${songIndex}].contributors[${contribIndex}].id`, 333 c.id.toString(), 334 ); 335 formData.append( 336 `albumSongs[${songIndex}].contributors[${contribIndex}].artistName`, 337 c.fullName, 338 ); 339 formData.append( 340 `albumSongs[${songIndex}].contributors[${contribIndex}].role`, 341 c.role, 342 ); 343 }); 344 }); 345 await axiosInstance.post("/musical-entity/publish/album", formData, { 346 headers: { "Content-Type": "multipart/form-data" }, 347 }); 348 } else { 349 formData.append("link", link); 350 appendContributors(formData, contributors, "contributors"); 351 await axiosInstance.post("/musical-entity/publish/song", formData, { 352 headers: { "Content-Type": "multipart/form-data" }, 353 }); 354 } 322 355 323 356 toast.success( … … 612 645 {contributors.map((contributor) => ( 613 646 <div 614 key={contributor. username}647 key={contributor.id} 615 648 className="flex items-center gap-2 bg-[#282828] rounded-full py-1.5 px-3" 616 649 > … … 626 659 <button 627 660 type="button" 628 onClick={() => 629 handleRemoveContributor(contributor.username) 630 } 661 onClick={() => handleRemoveContributor(contributor.id)} 631 662 className="text-gray-400 hover:text-red-400 transition-colors" 632 663 > … … 687 718 searchResults.map((artist) => ( 688 719 <button 689 key={artist. username}720 key={artist.id} 690 721 type="button" 691 722 onClick={() => handleAddContributor(artist)} … … 808 839 {song.contributors.map((contributor) => ( 809 840 <div 810 key={contributor. username}841 key={contributor.id} 811 842 className="flex items-center gap-2 bg-[#1a1a2e] rounded-full py-1 px-2.5 text-sm" 812 843 > … … 826 857 handleRemoveAlbumSongContributor( 827 858 index, 828 contributor. username,859 contributor.id, 829 860 ) 830 861 } … … 902 933 (artist) => ( 903 934 <button 904 key={artist. username}935 key={artist.id} 905 936 type="button" 906 937 onClick={() => -
frontend/src/utils/types.ts
r92db381 rdb5fb23 121 121 122 122 export interface Contributor { 123 username: string;123 id: number; 124 124 fullName: string; 125 125 role: string; … … 127 127 128 128 export interface ArtistSearchResult { 129 id: number; 129 130 username: string; 130 131 fullName: string;
Note:
See TracChangeset
for help on using the changeset viewer.
