react

App.js

// 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

Container.js

// 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

spring

Model

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
)

Repository

interface

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>
}

Service

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)

Controller

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")
    }
}

데이터를 조회할 때 GetMapping

데이터를 추가할 때 PostMapping

데이터를 수정 및 업데이트 할 때 PutMapping