rest-api 관련 실습 진행 (blog 예제)
•
react
◦
링크 테이블 추가 후 코드 내용
import { Box, Button, FormControl, FormLabel, Grid2, Paper, Stack, styled, Switch, TextField, Typography } from "@mui/material";
import { useForm } from "react-hook-form";
import { favAPI } from "../../api/services/favorite";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
const FavForm = () => {
// const { favId } = useParams();
const { state } = useLocation();
const navigate = useNavigate();
const { register, formState: { errors }, handleSubmit, setValue, } = useForm();
const [uploadFile, setUploadFile] = useState();
const imgRef = useRef();
const uploadFilePreview = () => {
const reader = new FileReader();
reader.readAsDataURL(imgRef.current.files[0]);
reader.onloadend = () => {
setUploadFile(reader.result);
};
};
useEffect(() => {
if (state) {
state.image && setShowFileInput(false);
setValue("title", state.title);
setValue("url", state.url);
}
}, []);
const [showFileInput, setShowFileInput] = useState(true);
const toggleFileInput = () => {
setShowFileInput(prev => !prev);
}
const onSubmit = async (data) => {
try {
const formData = new FormData();
Object.keys(data).forEach(key => {
formData.append(key, data[key]);
});
if (data.logo.length) {
formData.append("image", data.logo[0]);
} else {
formData.delete("image");
if (showFileInput) {
formData.append("deleteImage", true);
}
}
if (state) {
formData.append("id", state.id);
const res = await favAPI.modifyFav(formData);
} else {
const res = await favAPI.writeFav(formData);
}
navigate("/favorite")
} catch (error) {
console.error(error);
}
}
return (
<Paper
elevation={3}
sx={{
padding: 4,
maxWidth: 600,
margin: "auto",
marginTop: 4,
}}
>
<Box component="form" onSubmit={handleSubmit(onSubmit)} noValidate>
<Grid2 container spacing={3}>
<FormControl fullWidth>
<FormLabel htmlFor="title">사이트 이름</FormLabel>
<TextField
autoComplete="title"
{...register("title", { required: true })}
fullWidth
id="title"
placeholder="구글"
error={errors.title ? true : false}
helperText={errors.title && "사이트 이름은 필수값입니다."}
/>
</FormControl>
<FormControl fullWidth>
<FormLabel htmlFor="url">URL</FormLabel>
<TextField
id="url"
fullWidth
{...register("url", {
required: true,
pattern: /((?:https\:\/\/)|(?:http\:\/\/)|(?:www\.))?([a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(?:\??)[a-zA-Z0-9\-\._\?\,\'\/\\\+&%\$#\=~]+)/
})}
autoComplete="url"
placeholder="www.google.com"
variant="outlined"
error={errors.url ? true : false}
helperText={errors.url && "주소 규격에 맞지 않습니다."}
/>
</FormControl>
<FormControl fullWidth>
<FormLabel component="legend">로고 이미지</FormLabel>
{state?.image && (
<Stack
direction="row"
spacing={1}
sx={{ alignItems: "center", marginBottom: 1 }}
>
<Typography>변경</Typography>
<AntSwitch
defaultChecked
onClick={toggleFileInput}
/>
<Typography>유지</Typography>
</Stack>
)}
{showFileInput &&
<>
<TextField
type="file"
{...register("logo")}
slotProps={{ htmlInput : {"accept": "image/*"}}}
variant="outlined"
inputRef={imgRef}
onChange={uploadFilePreview}
/>
</>
}
{ (!showFileInput || uploadFile) &&
<Box sx={{ textAlign: "center", marginTop: 2 }}>
<img
src={uploadFile ? uploadFile : `${process.env.REACT_APP_SERVER}/img/${state.image.saved}`}
alt="Current Logo"
style={{ maxWidth: "100%", maxHeight: 200 }}
/>
</Box>
}
</FormControl>
<Button
type="submit"
fullWidth
variant="contained"
>
{state ? "수정하기" : "등록하기"}
</Button>
</Grid2>
</Box>
</Paper>
);
}
const AntSwitch = styled(Switch)(({ theme }) => ({
width: 36,
height: 20,
padding: 0,
display: "flex",
"&:active": {
"& .MuiSwitch-thumb": {
width: 15,
},
"& .MuiSwitch-switchBase.Mui-checked": {
transform: "translateX(16px)",
},
},
"& .MuiSwitch-switchBase": {
padding: 2,
"&.Mui-checked": {
transform: "translateX(16px)",
color: "#fff",
"& + .MuiSwitch-track": {
backgroundColor: "#1890ff",
opacity: 1,
},
},
},
"& .MuiSwitch-thumb": {
width: 16,
height: 16,
borderRadius: 8,
transition: theme.transitions.create(["width"], {
duration: 200,
}),
},
"& .MuiSwitch-track": {
borderRadius: 10,
backgroundColor: "rgba(0,0,0,.25)",
opacity: 1,
},
}));
export default FavForm;
JavaScript
복사
◦
FavList
import { useNavigate } from "react-router-dom";
import { Box, Button } from "@mui/material";
import { useEffect, useReducer, useState } from "react";
import { favAPI } from "../../api/services/favorite";
import FavAccordion from "./FavAccordion";
const favListReducer = (state, action) => {
switch (action.type) {
case "SET_FAVS":
return action.payload;
case "DELETE_FAV":
return state.filter(fav => fav.id != action.payload.id);
}
}
const FavList = () => {
const [favList, dispatch] = useReducer(favListReducer, []);
const navigate = useNavigate();
const getFavList = async () => {
const res = await favAPI.getFavList();
dispatch({type: "SET_FAVS", payload: res.data});
};
useEffect(() => {
getFavList();
}, []);
return (
<>
<Button onClick={() => navigate("/favorite/write")}>즐겨찾기 추가</Button>
<Box sx={{width: "500px"}}>
{favList.map((fav) => (
<FavAccordion key={fav.id} fav={fav} dispatch={dispatch}/>
))}
</Box>
</>
);
};
export default FavList;
JavaScript
복사
◦
FavAccordion
import {
Accordion,
AccordionDetails,
AccordionSummary,
Avatar,
Box,
Button,
styled,
Typography,
} from "@mui/material";
import ArrowForwardIosSharpIcon from "@mui/icons-material/ArrowForwardIosSharp";
import { useState } from "react";
import Swal from "sweetalert2";
import { favAPI } from "../../api/services/favorite";
import { useNavigate } from "react-router-dom";
const FavAccordion = ({fav, dispatch}) => {
const navigate = useNavigate();
const [isExpand, setIsExpand] = useState(false);
const handleExpand = () => setIsExpand(prev=> !prev);
const handleMove = () => {
let urlLink = fav.url;
if (!fav.url.startsWith("http://") && !fav.url.startsWith("https://")) {
urlLink = `http://${fav.url}`;
}
window.open(urlLink, "_blank", "noopener, noreferrer");
}
const handleDelete = () => {
Swal.fire({
title: "확실해요?",
text: "한 번 삭제하면 다시 되돌릴 수 없어요",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "응! 지워줘."
}).then(async (result) => {
if (result.isConfirmed) {
try {
const res = await favAPI.deleteFav(fav.id);
if (res.status == 200) {
Swal.fire({
text: "삭제되었습니다.",
icon: "success"
});
dispatch({type:"DELETE_FAV", payload: fav})
}
} catch (err) {
console.error(err);
}
}
});
}
const handleModify = () => {
navigate(`/favorite/modify/${fav.id}`, { state : fav });
}
console.log(fav);
return (
<MyAccordion expanded={isExpand} onClick={handleExpand}>
<MyAccordionSummary>
<Box sx={{display: "flex", justifyContent: "space-between", width: "100%"}}>
<Avatar alt={fav.title} src={`${process.env.REACT_APP_SERVER}/img/${fav.image?.saved}`} />
<span>{fav.id}</span>
<span style={{width: "40%"}}>{fav.title}</span>
<Button onClick={handleModify}>수정</Button>
<Button onClick={handleDelete}>삭제</Button>
</Box>
</MyAccordionSummary>
<MyAccordionDetails>
<Button onClick={handleMove}>{fav.url}</Button>
</MyAccordionDetails>
</MyAccordion>
);
};
const MyAccordion = styled((props) => (
<Accordion disableGutters elevation={0} square {...props} />
))(({ theme }) => ({
border: `1px solid ${theme.palette.divider}`,
"&:not(:last-child)": {
borderBottom: 0,
},
"&::before": {
display: "none",
},
}));
const MyAccordionSummary = styled((props) => (
<AccordionSummary
expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: "0.9rem" }} />}
{...props}
/>
))(({ theme }) => ({
backgroundColor: "rgba(0, 0, 0, .03)",
flexDirection: "row-reverse",
"& .MuiAccordionSummary-expandIconWrapper.Mui-expanded": {
transform: "rotate(90deg)",
},
"& .MuiAccordionSummary-content": {
marginLeft: theme.spacing(1),
},
...theme.applyStyles("dark", {
backgroundColor: "rgba(255, 255, 255, .05)",
}),
}));
const MyAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
padding: theme.spacing(2),
borderTop: "1px solid rgba(0, 0, 0, .125)",
}));
export default FavAccordion;
JavaScript
복사
•
springboot - 회원가입 인증 관련 내용 진행
◦
AuthController
package com.kosta.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kosta.domain.SignUpRequest;
import com.kosta.domain.UserDeleteRequest;
import com.kosta.domain.UserResponse;
import com.kosta.domain.UserUpdateRequest;
import com.kosta.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
// 회원가입
@PostMapping("")
public ResponseEntity<UserResponse> signUp(@RequestBody SignUpRequest signUpRequest) {
log.info("[signUp] 회원가입 진행. 요청정보 : {}", signUpRequest);
UserResponse userResponse = authService.signUp(signUpRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(userResponse);
}
// 회원 전체 리스트
@GetMapping("")
public ResponseEntity<List<UserResponse>> getUserList() {
log.info("[getUserList] 회원 전체 조회");
List<UserResponse> userList = authService.getUserList();
return ResponseEntity.ok(userList);
}
// 회원 정보 수정
@PatchMapping("")
public ResponseEntity<UserResponse> updateUser(@RequestBody UserUpdateRequest userUpdateReqeust) {
log.info("[updateUser] 회원 정보 수정. 수정 요청 정보 : {}", userUpdateReqeust);
UserResponse userResponse = authService.updateUser(userUpdateReqeust);
return ResponseEntity.ok(userResponse);
}
// 회원 삭제
@DeleteMapping("")
public ResponseEntity<?> userWithdrawal(@RequestBody UserDeleteRequest userDeleteRequest) {
log.info("[updateUser] 회원 삭제. 삭제 요청 정보 : {}", userDeleteRequest);
authService.deleteUser(userDeleteRequest);
return ResponseEntity.ok(null);
}
}
Java
복사
◦
AuthService
package com.kosta.service;
import java.util.List;
import com.kosta.domain.SignUpRequest;
import com.kosta.domain.UserDeleteRequest;
import com.kosta.domain.UserResponse;
import com.kosta.domain.UserUpdateRequest;
public interface AuthService {
UserResponse signUp(SignUpRequest signUpRequest);
List<UserResponse> getUserList();
UserResponse updateUser(UserUpdateRequest userUpdateReqeust);
void deleteUser(UserDeleteRequest userDeleteRequest);
}
Java
복사
◦
AuthServiceImpl
package com.kosta.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.kosta.domain.SignUpRequest;
import com.kosta.domain.UserDeleteRequest;
import com.kosta.domain.UserResponse;
import com.kosta.domain.UserUpdateRequest;
import com.kosta.entity.User;
import com.kosta.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final UserRepository userRepository;
@Override
public UserResponse signUp(SignUpRequest signUpRequest) {
User user = User.builder().email(signUpRequest.getEmail()).name(signUpRequest.getName())
.password(signUpRequest.getPassword()).build();
User savedUser = userRepository.save(user);
return UserResponse.toDTO(savedUser);
}
@Override
public List<UserResponse> getUserList() {
List<User> userList = userRepository.findAll();
return userList.stream().map(UserResponse::toDTO).toList();
}
@Override
public UserResponse updateUser(UserUpdateRequest userUpdateReqeust) {
User user = userRepository.findByEmail(userUpdateReqeust.getEmail())
.orElseThrow(() -> new IllegalArgumentException("회원 정보 조회에 실패했습니다. [없는 이메일]"));
if (!user.getPassword().equals(userUpdateReqeust.getPassword())) {
throw new RuntimeException("비밀번호 입력 오류");
}
if (userUpdateReqeust.getName() != null)
user.setName(userUpdateReqeust.getName());
User updatedUser = userRepository.save(user);
return UserResponse.toDTO(updatedUser);
}
@Override
public void deleteUser(UserDeleteRequest userDeleteRequest) {
User user = userRepository.findByEmail(userDeleteRequest.getEmail())
.orElseThrow(() -> new IllegalArgumentException("회원 정보 조회에 실패했습니다. [없는 이메일]"));
if (!user.getPassword().equals(userDeleteRequest.getPassword())) {
throw new RuntimeException("비밀번호 입력 오류");
}
userRepository.delete(user);
}
}
Java
복사