포스트 작성 구현 전에 oauth 관련 설정 + 클래스를 만들어 놨었는데
이것 때문에 얼마나 고생했는지 ... post 관련된 controller, service 다 만들어놓고 테스트를 해보는데 계속 뚱딴지 같은 응답이 서버에서 날아왔다. json 형태도 아니고, postman 이용해서 모든 post 요청을 보낼 때마다 구글 로그인 관련 html이 redirect되어서 response로 전달되었다. 인텔리제이 내에서 테스트할 때는 expected: 200 ok , got: 302 found 가 계속 ....
뭐가 문젠지 한참 몇 시간을 들여다보고 고쳐보고 하다가 결국 그냥 oauth관련 서비스 클래스들을 다 없애고 관련 dependency까지 전부 다 삭제해주니까 완전 멀쩡히 돌아가는 테스트 ^^ 그냥 처음부터 밀어버릴걸
구글링하던 중 어떤 블로그의 이 문장을 본게 신의 한수였음
- 우리가 부트에서 시큐리티 스타터를 등록만 했는데 모든 인증 없는 컨트롤러 테스트가 깨지고, 웹에선 로그인 페이지로 리다이렉트 되는 이유가 여기에 있다 -
'뭐야 내 이야기잖아'
oauth 관련 환경설정은 마치고 제대로 구현은 하지 않은 상태로 post 관련 요청을 보내니 내가 모르는 어디선가 권한이 막혀서 구글 로그인을 하라는 html로 oauth가 redirect를 자동으로 했던 것 같다.
🤗 여기서 새로운 교훈 : 끝까지 구현 안 한 API나 기능은 다른 데다 보관하고 웬만해선 프로젝트에선 깔끔하게 삭제하자. 특히 외부 API 사용시 내가 컨트롤하지 않은 많은 자동 기능들이 구현되어 있으니 제발 경계할 것
아무튼 Post 작성 기능은 구현 완료 !
FE는 일단 그냥 대충 만들었다
데이터베이스에 성공적으로 올라온 것 확인 완료 !
🛠 수정 및 개선할 사항
1. 평점 입력를 단순 text 입력이 아닌 다른 방법으로 구현해보기 -> 별 이미지를 사용한다거나 하는 비주얼적인 UI로 변경
2. 게시판 관련 라이브러리를 이용해서 사진, 글꼴, 글씨 크기 등을 조절할 수 있도록 구현
h2console에 올라온 거 보고 박수쳤다
그렇지만 아직 갈 길이 구만리이다 화이탱탱
구현 코드
Backend
- Post
package com.frombooktobook.frombooktobookbackend.domain.post;
import com.frombooktobook.frombooktobookbackend.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@NoArgsConstructor
@Entity
public class Post extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// private String user;
@Column(nullable = false)
private String bookName;
private String title;
private String bookAuthor;
private int rate;
private String content;
@Builder
public Post(String bookName, String bookAuthor, int rate, String content, String title) {
this.bookName = bookName;
this.bookAuthor = bookAuthor;
this.rate=rate;
this.content = content;
this.title = title;
}
public void setTitle(String title) {
this.title = title;
}
}
- PostCreateRequestDto
package com.frombooktobook.frombooktobookbackend.controller.post;
import com.frombooktobook.frombooktobookbackend.domain.post.Post;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@NoArgsConstructor
public class PostCreateRequestDto {
private String bookName;
private String bookAuthor;
private String title;
private String content;
private int rate;
@Builder
public PostCreateRequestDto(String bookName, String bookAuthor, String content, int rate, String title) {
this.bookName = bookName;
this.bookAuthor = bookAuthor;
this.rate=rate;
this.content = content;
if(title!=null) {
this.title = title;
} else {
this.title = bookName+"을 읽고";
}
}
public Post toEntity() {
return Post.builder()
.bookName(bookName)
.bookAuthor(bookAuthor)
.title(title)
.content(content)
.rate(rate)
.build();
}
}
- PostResponseDto
package com.frombooktobook.frombooktobookbackend.controller.post;
import com.frombooktobook.frombooktobookbackend.domain.post.Post;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class PostResponseDto {
private String bookName;
private String bookAuthor;
private String title;
private String content;
private int rate;
public PostResponseDto(Post post) {
this.bookName=post.getBookName();
this.bookAuthor=post.getBookAuthor();
this.title=post.getTitle();
this.content=post.getContent();
this.rate=post.getRate();
}
}
- PostRepository
package com.frombooktobook.frombooktobookbackend.domain.post;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface PostRepository extends JpaRepository<Post,Long> {
Optional<Post> findById();
}
-PostService
package com.frombooktobook.frombooktobookbackend.service;
import com.frombooktobook.frombooktobookbackend.controller.post.PostListResponseDto;
import com.frombooktobook.frombooktobookbackend.controller.post.PostResponseDto;
import com.frombooktobook.frombooktobookbackend.domain.post.Post;
import com.frombooktobook.frombooktobookbackend.domain.post.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class PostService {
private final PostRepository postRepository;
@Transactional
public Post savePost(Post post) {
postRepository.save(post);
return post;
}
public PostResponseDto findById(Long id) {
Post post = postRepository.findById(id).orElseThrow(()->
new IllegalArgumentException("해당 게시글이 없습니다. id=" +id));
return new PostResponseDto(post);
}
}
- PostController
package com.frombooktobook.frombooktobookbackend.controller.post;
import com.frombooktobook.frombooktobookbackend.domain.post.Post;
import com.frombooktobook.frombooktobookbackend.service.PostService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RestController
public class PostController {
private final PostService postService;
@PostMapping("/post/write")
public ResponseEntity<PostResponseDto> CreatePost(
@RequestBody PostCreateRequestDto requestDto) {
try {
Post post = postService.savePost(requestDto.toEntity());
if(post == null) {
System.out.println("post가 null입니다.");
}
return ResponseEntity.ok()
.body(new PostResponseDto(post));
} catch(Exception e) {
System.out.println(e);
}
return ResponseEntity.ok()
.body(new PostResponseDto(requestDto.toEntity()));
}
}
구현 코드
Frontend
PostWriteForm < PostWriteTemplate < PostWritePage
- PostWriteForm
import { useState } from 'react';
import style from './PostWrite.module.css';
import Axios from 'axios';
import PostService from '../../service/PostService';
const PostWriteForm = () => {
const [postForm, setPostForm] = useState({
bookName: '',
bookAuthor: '',
title: '',
content: '',
rate: -1,
});
const onChange = (e) => {
const changingField = e.target.name;
setPostForm({
...postForm,
[changingField]: e.target.value,
});
console.log(postForm);
};
const onSubmit = (e) => {
e.preventDefault();
console.log('onSubmit');
PostService.createPost(postForm).then(() => {
alert('등록 완료!');
});
};
return (
<div className={style.formBlock}>
<h3>독후감 작성</h3>
<form className={style.form} onSubmit={onSubmit}>
<input
className={style.styledInput}
autoComplete="bookName"
name="bookName"
placeholder="책 제목"
onChange={onChange}
/>
<input
className={style.styledInput}
autoComplete="bookAuthor"
name="bookAuthor"
placeholder="저자"
onChange={onChange}
/>
<input
className={style.styledInput}
autoComplete="title"
name="title"
placeholder="독후감 제목"
onChange={onChange}
/>
<input
className={style.styledInput}
name="rate"
placeholder="평점"
type="number"
onChange={onChange}
/>
<textarea
className={style.styledInput}
name="content"
placeholder="나만의 독후감을 작성하세요."
type="text"
onChange={onChange}
/>
<button
className={style.styledButton}
type="submit"
onSubmit={onSubmit}
>
save
</button>
</form>
</div>
);
};
export default PostWriteForm;
- PostWriteTemplate
import style from './PostWrite.module.css';
const PostWriteTemplate = ({ children }) => {
return (
<div className={style.PostWriteTemplateBlock}>
<div className={style.whiteBox}>{children}</div>
</div>
);
};
export default PostWriteTemplate;
- PostWritePage
import PostWriteTemplate from '../components/post/PostWriteTemplate';
import PostWriteForm from '../components/post/PostWriteForm';
import Header from '../components/Header';
const PostWrite = () => {
return (
<>
<PostWriteTemplate>
<PostWriteForm />
</PostWriteTemplate>
</>
);
};
export default PostWrite;
- PostService
import Axios from 'axios';
class PostService {
createPost = (postForm) => {
return Axios.post('/post/write', {
bookName: postForm.bookName,
bookAuthor: postForm.bookAuthor,
title: postForm.title,
content: postForm.content,
rate: postForm.rate,
});
};
}
export default new PostService();
-PostWrite.module.css
.PostWriteTemplateBlock {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background: rgb(194, 194, 194);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.whiteBox {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.025);
padding: 2rem;
width: 66%;
background: white;
border-radius: 2px;
}
.formBlock {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.formBlock .h3 {
font-size: 1.1rem;
margin: 0;
color: rgba(0, 0, 0, 0.025);
margin-bottom: 1rem;
}
.form {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 80%;
}
.styledInput {
font-size: 1rem;
border: none;
border-bottom: 1px solid darkslategray;
padding-bottom: 1rem;
outline: none;
width: 100%;
margin-top: 1rem;
}
.styledInput:focus {
color: rgb(92, 104, 74);
border-bottom: 1px solid rgb(25, 43, 43);
}
.styledButton {
border: none;
border-radius: 4px;
font-size: 1.125rem;
font-weight: bold;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
margin-top: 2rem;
width: 50%;
color: white;
outline: none;
cursor: pointer;
background: rgb(19, 75, 61);
}
.styledButton:hover {
background: rgb(30, 122, 110);
}
- App.js
import { useEffect, useState } from 'react';
import './App.css';
import axios from 'axios';
import { Router, Route, Routes } from 'react-router-dom';
import Home from './routes/Home';
import PostWritePage from './routes/PostWritePage';
import Header from './components/Header';
import PostListPage from './routes/PostListPage';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/post/write" element={<PostWritePage />} />
</Routes>
);
}
export default App;
루트 정해주면 끝 !
'Project > FromBookToBook' 카테고리의 다른 글
[FBTB] 3. 로그인 기능 구현 (with Oauth2) (0) | 2022.04.07 |
---|---|
[FBTB] 2. 독후감 목록 기능 구현 (0) | 2022.04.01 |
[Error] PostControllerTest 중 에러 (0) | 2022.03.29 |
[Error] UserControllerTest 중 Error (0) | 2022.03.28 |
spring - react , 백엔드-프론트엔드 연동하기 연습 (0) | 2022.03.27 |