// CSS 스타일 불러오기
import './App.css'
// React의 useState와 useEffect 훅 가져오기
import { useState, useEffect } from 'react' //import {useState, useEffect} from 'react'
// Axios 라이브러리로 API 요청을 보내기 위해 가져오기
import axios from 'axios'
// Container 컴포넌트 가져오기
import Container from './Container.js'
// 백엔드 서버 URL 설정
const SERVER_URL = '<http://localhost:8080/api/songs>'
const App = () => {
// 노래 목록을 저장할 상태 변수
const [songs, setSongs] = useState([])
// 서버에서 노래 데이터를 가져오는 비동기 함수
const getSong = async () => { //const getSong = async () => try { const res = awiat axios.get(SERVER_URL)
try {
// 서버에 GET 요청을 보내고 응답 데이터를 res에 저장
const res = await axios.get(SERVER_URL)
console.log(res)
// 가져온 노래 데이터를 상태 변수에 저장
setSongs(res.data)
} catch (err) {
console.log(err)
// 에러 발생 시 빈 배열로 초기화
setSongs([])
}
}
// 컴포넌트가 처음 렌더링될 때 한 번 실행
useEffect(() => {
getSong()
}, [])
return (
<div>
{/* Header 컴포넌트 렌더링 */}
<Header />
{/* Playlist 컴포넌트에 props로 제목과 노래 목록 전달 */}
<Playlist
title="프로그래밍하면서 듣고 싶은 노래"
listSong={songs}/>
</div>
)
}
// 상단 제목을 렌더링하는 컴포넌트
const Header = () => {
return (
<h1>React 프로그래밍</h1>
)
}
// 노래 목록을 렌더링하는 Playlist 컴포넌트
// props를 구조 분해 할당으로 받아옴
const Playlist = ({ title, listSong }) => {
return (
<div className='playlist'>
{/* 제목 표시 */}
<div className="playlist">{title}</div>
{
// 노래 리스트를 순회하며 각 노래를 Container 컴포넌트로 렌더링
listSong.map(song =>
<Container key={song.id} song={song}/>
)
}
</div>
)
}
// App 컴포넌트를 기본 내보내기로 설정
export default App
// React의 useState 훅 가져오기
import { useState } from 'react'
// React-Icons 라이브러리에서 별 아이콘 가져오기
import { FaStar } from 'react-icons/fa'
// 노래 정보를 렌더링하는 Container 컴포넌트
const Container = (props) => {
// 가사가 열려 있는지 상태를 저장 (초기값: false)
const [lyricsExpanded, setLyricsExpanded] = useState(false)
// 가사 토글 함수: 현재 상태를 반전시킴
const toggleLyrics = () => {
setLyricsExpanded(prevState => !prevState)
}
return (
<>
{/* 컨테이너 전체 */}
<div className="container">
{/* 랜덤 이미지를 표시하고 클릭 시 가사를 토글 */}
<img
src={`https://picsum.photos/600/150?random=${props.song.id}`}
alt={`랜덤 이미지 ${props.song.id}`}
onClick={toggleLyrics}
/>
{/* 유튜브 검색 링크 */}
<a
href={`https://www.youtube.com/results?search_query=${props.song.title}`}
target="_blank"
rel="noreferrer">
{/* 노래 제목과 가수 표시 */}
<div className="song-title">
{`${props.song.title} (${props.song.singer})`}
</div>
</a>
{/* 별점 출력 */}
<div className="song-rating">
{/* Spread 연산자로 props.song.rating만큼 FaStar 컴포넌트를 생성 */}
{[...Array(props.song.rating)].map((_, index) => (
<FaStar key={index} />
))}
</div>
</div>
{/* 가사 출력 (가사가 존재하고 lyricsExpanded가 true일 때) */}
{props.song.lyrics && lyricsExpanded && (
<pre>
{props.song.lyrics}
</pre>
)}
</>
)
}
export default Container
package kr.ac.kumoh.s20230000.w24w13Backend.model
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
@Document(collection = "songs") //@Document(collection = "songs")
data class Song(// data
@Id val id: String? = null, //@Id val id: string:
val title: String,
val singer: String,
val rating: Int,
val lyrics: String
)
package kr.ac.kumoh.s20230000.w24w13Backend.repository
import kr.ac.kumoh.s20230000.w24w13Backend.model.Song
import org.springframework.data.mongodb.repository.MongoRepository
// Song은 사용할 document, String은 _id의 type
interface SongRepository : MongoRepository<Song,String> { //interface MongoRepository<Song, String>
// 기본적인 메소드는 모두 구현해 줌
// 이렇게 추가하면 알아서 구현해 줌
fun findBySinger(singer: String): List<Song>
}
package kr.ac.kumoh.s20230000.w24w13Backend.service
import kr.ac.kumoh.s20230000.w24w13Backend.model.Song
import kr.ac.kumoh.s20230000.w24w13Backend.repository.SongRepository
import org.springframework.stereotype.Service
@Service //Service
class SongService(private val repository: SongRepository) { //private val repository : SongRepository
fun addSong(song: Song): Song = repository.save(song) //save
fun getAllSongs(): List<Song> = repository.findAll() //findAll
fun getSongById(id: String): Song? = repository.findById(id).orElse(null) //findById(id), orElse(null)
fun getSongBySinger(singer: String): List<Song> = repository.findBySinger(singer) //findBySinger(singer)
fun updateSong(id: String, song: Song): Song? {
val songTarget = repository.findById(id) //findById(id)
return if (songTarget.isPresent) { //isPresent
val oldSong = songTarget.get() //get()
val updatedSong = oldSong.copy( //copy(바꿀 인자)
title = song.title,
singer = song.singer,
rating = song.rating,
lyrics = song.lyrics
)
repository.save(updatedSong) //save(업데이트 내용)
} else {
null
}
}
fun deleteSong(id: String): Boolean {
return if (repository.existsById(id)) {
repository.deleteById(id)
true
} else {
false
}
}
}
findById(id), findAll(), orElse(null), existById(id), deleteById(id), save(song)
package kr.ac.kumoh.s20230000.w24w13Backend.controller
import kr.ac.kumoh.s20230000.w24w13Backend.model.Song
import kr.ac.kumoh.s20230000.w24w13Backend.service.SongService
import org.springframework.web.bind.annotation.*
@RestController //RestController
@RequestMapping("/api/songs") //RequestMapping()
@CrossOrigin(origins = ["<http://localhost:3000>"]) //CrossOrigin(origins
class SongController(private val service: SongService) {
@PostMapping //추가 @PostMapping
fun addSong(@RequestBody song: Song): Song = service.addSong(song)
@GetMapping //검색 @GetMapping
fun getAllSongs(): List<Song> = service.getAllSongs()
@GetMapping("/{id}") //검색 @GetMapping
fun getSongById(@PathVariable id: String): Song? = service.getSongById(id)
@GetMapping("/singer/{singer}")
fun getSongBySinger(@PathVariable singer: String): List<Song> = service.getSongBySinger(singer)
@PutMapping("/{id}") // @PutMapping
fun updateSong(@PathVariable id: String, @RequestBody songDetails: Song): Song? = service.updateSong(id, songDetails)
@DeleteMapping("/{id}") //DeleteMapping
fun deleteSong(@PathVariable id: String): Map<String, String> {
return if (service.deleteSong(id))
mapOf("status" to "deleted")
else
mapOf("status" to "not found")
}
}