사람들이 많이 사용하고 있는 중고 거래 플랫폼을 REST API로 구현해보는 프로젝트
💡 ① 중고 물품 관리, 댓글, 제안
git clone
이후, application.properties
의 jwt.secret
값을 변경해야 작동합니다.
중고 물품 관리 API
✅ 표시: 로그인 시 JWT 발급 → Auth(Type=Bearer Token): JWT 입력
1. POST /items ✅
Request Body:
{
"title": "중고 맥북 팝니다",
"description": "2019년 맥북 프로 13인치 모델입니다",
"minPriceWanted": 1000000
}
Response Body:
4. PUT /items/{itemId} ✅
Request Body:
{
"title": "응 안팔아",
"description": "걍 내가 쓸꺼야",
"minPriceWanted": 5000000
}
Response Body:
중고 물품 댓글 API
✅ 표시: 로그인 시 JWT 발급 → Auth(Type=Bearer Token): JWT 입력
3. PUT /items/{itemId}/comments/{commentId} ✅
Request Body:
{
"content": "1000000 정도면 고려 가능합니다"
}
Response Body:
4. PUT /items/{itemId}/comments/{commentId}/reply ✅
Request Body:
{
"reply": "ㄴㄴ안됨"
}
Response Body:
구매 제안 API
✅ 표시: 로그인 시 JWT 발급 → Auth(Type=Bearer Token): JWT 입력
1. POST /items/{itemId}/proposals ✅
Request Body:
{
// 구매 제안을 올린 구매자
"suggestedPrice": 1000000
}
Response Body:
2. GET http://localhost:8080/items/1/proposal?page=1 ✅
Response Body:
3. PUT /items/{itemId}/proposals/{proposalId} ✅
Request Body:
{
// 구매 제안을 올린 구매자
"suggestedPrice": 7777777
}
Response Body:
5. PUT /items/{itemId}/proposals/{proposalId} ✅
Request Body:
{
// 중고 물품을 올린 판매자
"status": "거절" // "수락"도 가능
}
Response Body:
6. PUT /items/{itemId}/proposals/{proposalId} ✅
Request Body:
{
// 구매 제안을 올린 구매자
"status": "확정"
}
Response Body:
7. 그 외
PUT /items/{itemId}/proposals/{proposalId}
-
3번의 PUT(제안 가격 변경)의 경우 구매 제안 작성자만 수정이 가능하며,
status
가 "제안",SuggestedPrice
가 null이 아닐 때만 작동한다. -
5번의 PUT(수락, 거절)의 경우 물품 등록 작성자만 수정 가능하며, 상태가 수락, 거절이 되었을 경우 구매 제안 작성자는 글을 수정할 수 없다.
-
6번의 PUT(구매 확정)의 경우 구매 제안 작성자만 수정이 가능하며, 현재 "수락" 상태이고 Request로 받는
status
가 "확정"이면status
는 "확정"으로 변한다.물품 등록 게시물 또한 "판매 완료"가 된다. 이 상태에서 게시물, 구매 제안을 지울 수 없다.
또한 자동으로 모든 구매제안은 "거절" 상태가 된다.
-
ROLE_ADMIN
의 권한을 가지고 있다면 구매 제안 API의 모든 기능을 사용할 수 있으며, "판매 완료" 상태가 되어도 수정이나 삭제가 가능하다.
💡 ② 사용자 인증, 관계 설정, 접근 권한 설정
로그인(토큰 발급), 회원가입 API
1. POST /users/login
Request Body:
{
// ROLE_ADMIN 권한을 가진 TEST 계정 존재
"userId": "운영자",
"password": "asdf"
}
Response Body:
2. GET /users/login
Request Body:
(JSON Data)
{
// 회원가입
"userId": "유저",
"password": "asdf"
}
Response Body:
DB:
ERD 수정 및 코드 수정 API
1. 기존 Entity(Item, Comment, Proposal)의 writer, password 삭제 -> User Enitiy와 1:N 매핑
📅 Market ERD 참고
2. ERD 변경에 의한 제대로 된 기능 작동을 위한 코드 수정
📁 REST API - 💡 ① 중고 물품 관리, 댓글, 제안 참고
3. 자세한 수정 사항
ISSUE : 2️⃣ DAY 2 / 관계 설정하기 참고
PULL REQUEST : 관계 설정 및 관계 변경으로 인한 코드 변경 #8 참고
ROLE STATUS 추가
1. Authentication 추가로 인한 등록(삭제, 변경 등), 조회를 사용자 정보에 따라 제한되거나 가능하게 변경
ROLE_ADMIN
,ROLE_USER
두 권한이 존재하며,ROLE_ADMIN
은 💡 ① 중고 물품 관리, 댓글, 제안의 모든 기능 사용 가능확정
상태의 구매 제안을 삭제하는 등 제한되어 있는 기능도 사용할 수 있다.
2. 자세한 수정 사항
ISSUE : 3️⃣ DAY 3/ 기능 접근 설정하기 참고
📁 REST API 돌아가기
💡 ③ UI 구현
HOME(Page 관련) - GET / -> (redirect)/items/view
글이 10개 이상 넘어가면 게시물 페이지를 넘길 수 있다. 댓글도 가능하며 댓글은 15개가 limit으로 잡혀 있다.
펼쳐 보기
✨ 2023-06-29: Repository 생성, DTO 추가, SalesItem MVC 구조
Create: Git Repository - 'MiniProject_Basic_LimHyoungTaek'
- Spring Web
- Spring Boot DevTools
- Spring Data JPA
- Lombok
- Sqlite
Add:
- DTO(SalesItem, Negotiation, Comment)
- Controller, repository, entity, service associated (with SalesItem)
✨ 2023-06-30: ResponseDTO 추가, TODO 구현
Add:
- DTO(ResponseDto)
TODO:
POST /items
GET /items?page={page}&limit={limit}
GET /items/{itemId}
PUT /items/{itemId}
DELETE /items/{itemId}
✨ 2023-07-03: DAY 1 / 중고 물품 관리 요구사항, 중고 물품 댓글 MVC 구조
DAY 1 / 중고 물품 관리 요구사항
1️⃣ [POST] /items
ItemController.create()
, ItemService.createItem()
: 누구든지 중고 거래를 목적으로 물품에 대한 정보를 등록할 수 있다.
ItemEntity - @NotNull
: 이때 반드시 포함되어야 하는 내용은 제목, 설명, 최소 가격, 작성자이다.
ItemService.validPW()
: 또한 사용자가 물품을 등록할 때, 비밀번호 항목을 추가해서 등록한다.
ItemService.createItem()
: 최초로 물품이 등록될 때, 중고 물품의 상태는 판매중 상태가 된다.
2️⃣ [GET] /items?page={page}&limit={limit}
ItemService.readItemsPaged()
, Return Type Page<ItemPageInfoDto>
: 등록된 물품 정보는 누구든지 열람할 수 있다.
페이지 단위 조회가 가능하다.
ItemController.readAll()
, ItemController.readOne()
: 전체 조회, 단일 조회 모두 가능하다.
3️⃣ [GET] /items/{itemId}
ItemController.readOne()
: 전체 조회, 단일 조회 모두 가능하다.
4️⃣ [PUT] /items/{itemId}
ItemController.update()
, ItemService.updateItem()
: 등록된 물품 정보는 수정이 가능하다.
ItemService.validPW()
: 이때, 물품이 등록될 때 추가한 비밀번호를 첨부해야 한다.
5️⃣ [DELETE] /items/{itemId}
ItemController.delete()
, ItemService.deleteItem()
: 등록된 물품 정보는 삭제가 가능하다.
ItemService.validPW()
: 이때, 물품이 등록될 때 추가한 비밀번호를 첨부해야 한다.
6️⃣ [PUT] /items/{itemId}/image
ItemController.uploadImage()
, ItemService.uploadItemImage()
: 등록된 물품 정보에 이미지를 첨부할 수 있다.
ItemService.validPW()
: 이때, 물품이 등록될 때 추가한 비밀번호를 첨부해야 한다.
7️⃣ 그 외 추가 및 수정사항
getItemById()
: 해당하는 ID가 없을 경우, Not Found 예외 처리하는 과정을 메서드로 분리
validPW()
: Password를 검사하는 부분을 메서드로 분리
ResponseDto
: Controller의 Return Type을 ResponseDto로 수정 후 ResponseBody 출력 형식 message로 변경
ContentinfoDto
: ItemController.readOne()
에서 title, description, minPriceWanted, status만 보이게 Dto 설정
PageinfoDto
: ItemController.readAll()
에서 id, title, description, minPriceWanted, status만 보이게 Dto 설정
imageUrl -> add @JsonInclude(JsonInclude.Include.NON_NULL) Null 값 일때 미출력
중고 물품 댓글 MVC 구조
Add:
- CommentController
- CommentEntity
- CommentRepository
- CommentService
TODO:
POST /items/{itemId}/comments
GET /items/{itemId}/comments
PUT /items/{itemId}/comments/{commentId}
PUT /items/{itemId}/comments/{commentId}/reply
DELETE /items/{itemId}/comments/{commentId}
✨ 2023-07-04: DAY 2 / 중고 물품 댓글 요구사항
1️⃣ [POST] /items/{itemId}/comments
CommentController.createComment()
, CommentService.postComment()
: 등록된 물품에 대한 질문을 위하여 댓글을 등록할 수 있다.
CommentEntity - @NotNull
: 이때 반드시 포함되어야 하는 내용은 대상 물품, 댓글 내용, 작성자이다.
PasswordValidatable.validatePassword()
, CommentEntity - @Override
: 또한 댓글을 등록할 때, 비밀번호 항목을 추가해서 등록한다.
2️⃣ [GET] /items/{itemId}/comments
CommentController.readAllComment()
, CommentService.getCommentsPaged()
: 등록된 댓글은 누구든지 열람할 수 있다.
CommentService.getCommentsPaged()
, Return Type Page<CommentPageInfoDto>
: 페이지 단위 조회가 가능하다.
3️⃣ [PUT] /items/{itemId}/comments/{commentId}
CommentController.updateComment()
, CommentService.modifiedComment()
: 등록된 댓글은 수정이 가능하다.
PasswordValidatable.validatePassword()
, CommentEntity - @Override
: 이때, 댓글이 등록될 때 추가한 비밀번호를 첨부해야 한다.
4️⃣ [DELETE] /items/{itemId}/comments/{commentId}
CommentController.delete()
, CommentService.deleteComment()
: 등록된 댓글은 삭제가 가능하다.
PasswordValidatable.validatePassword()
, CommentEntity - @Override
: 이때, 댓글이 등록될 때 추가한 비밀번호를 첨부해야 한다.
5️⃣ [PUT] /items/{itemId}/comments/{commentId}/reply
CommentPageInfoDto
: 댓글에는 초기에 비워져 있는 답글 항목이 존재한다.
↳ 그래서 다른 Column과 다르게 @NotNull
을 붙이지 않았다. 대신 imageUrl
의 null
값을 숨길 때 처럼 @JsonInclude(JsonInclude.Include.NON_NULL)
을 붙였다.
CommentPageInfoDto
: 답글은 댓글에 포함된 공개 정보이다.
↳ 이 요구사항 때문에 위에서 언급한 @JsonInclude(JsonInclude.Include.NON_NULL)
도 추가하지 않을까 하다가 null
값일 경우, 답글이 보이지 않는 경우가 더 많다고 생각해서 유지하였다.
CommentService.modifiedReply()
: 만약 댓글이 등록된 대상 물품을 등록한 사람일 경우, 물품을 등록할 때 사용한 비밀번호를 첨부할 경우 답글 항목을 수정할 수 있다.
↳ 이 부분은 아래 토글을 열어 코드를 참고해주세요.
📄 CommentService.java - modifiedReply()
public class CommentService {
private final ItemRepository itemRepository;
private final ItemService itemService;
private final CommentRepository commentRepository;
// Post, Modifying Reply
public void modifiedReply(Long commentId, Long itemId, CommentDto comments) {
CommentEntity commentEntity = validateCommentByItemId(commentId, itemId);
ItemEntity itemEntity = itemService.getItemById(itemId);
// 1. 답글 작성자 != 물품 등록 작성자 -> 예외 처리
// 댓글에 답글을 달 수 있는 사용자는 물품 정보를 등록한 사용자 뿐
if (!itemEntity.getWriter().equals(comments.getWriter()))
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
// 2. 물품 등록 작성자 == 답글 작성자 라는건 위의 예외에서 증명
// 만약 댓글이 등록된 대상 물품을 등록한 사람일 경우
// -> 물품 등록 == 댓글 == 답글 다 같은 작성자이다.
if (commentEntity.getWriter().equals(comments.getWriter())) {
// 물품을 등록할 때 사용한 비밀번호를 첨부할 경우 답글 항목을 수정할 수 있다.
// 물품 등록 비밀번호 != 답글 비밀번호 -> 예외 처리
itemEntity.validatePassword(comments.getPassword());
}
// Save Reply
commentEntity.setReply(comments.getReply());
CommentDto.fromEntity(commentRepository.save(commentEntity));
}
}
6️⃣ 그 외 추가 및 수정사항
PageinfoDto
: ItemPageInfoDto
, CommentPageInfoDto
로 구분을 위해 자세하게 이름 설정
dto/mapping
으로 경로 설정
PasswordValidatable
: validPW
를 ItemEntity
와 CommentEntity
에서 받을 수 있게 interface
로 변경
각 Entity
에서 implements PasswordValidatable
하고 난 후, @Override
할 수 있게 변경
CommentService - validateCommentByItemId()
: 각 메서드마다 요청 댓글 유무, 대상 댓글이 대상 게시글의 댓글인지 확인하는 과정이 겹쳐서 따로 분리
✨ 2023-07-04: DAY 3 / 구매 제안 기본 CRUD 구조 생성
구매 제안 기본 CRUD 구조 생성
Add:
- ProposalController
- ProposalEntity
- ProposalRepository
- ProposalService
- ProposalPageInfoDto
TODO:
POST /items/{itemId}/proposal
GET /items/{itemId}/proposals?writer=Lim123&password=qwerty1234&page=1
PUT /items/{itemId}/proposals/{proposalId}
DELETE /items/{itemId}/proposals/{proposalId}
PUT /items/{itemId}/proposals/{proposalId}
✨ 2023-07-05: DAY 3 / 구매 제안 요구사항
중고 물품 댓글 MVC 구조
1️⃣ [POST] /items/{itemId}/proposals
ProposalController.createProposal()
, ProposalService.postOffer()
: 등록된 물품에 대하여 구매 제안을 등록할 수 있다.
NegotiationDto - @NotNull
: 이때 반드시 포함되어야 하는 내용은 대상 물품, 제안 가격, 작성자이다.
참고로 이전에 Entity에 붙어있던 @NotNull
은 다 Dto로 이동함.
PasswordValidatable.validatePassword()
, ProposalEntity - @Override
: 또한 구매 제안을 등록할 때, 비밀번호 항목을 추가해서 등록한다.
ProposalService.postOffer() - newProposal.setStatus("제안");
: 구매 제안이 등록될 때, 제안의 상태는 제안 상태가 된다.
2️⃣ [GET] /items/{itemId}/proposal?writer=lim123&password=1qaz2wsx&page=1
ProposalController.readAllProposal()
: 구매 제안은 대상 물품의 주인과 등록한 사용자만 조회할 수 있다.
ProposalService.findPagedOffer()
, ProposalRepository.findAll()
: 대상 물품의 주인은, 대상 물품을 등록할 때 사용한 작성자와 비밀번호를 첨부해야 한다.
이때 물품에 등록된 모든 구매 제안이 확인 가능하다.
ProposalService.findPagedOffer()
, ProposalRepository.findAllByItemIdAndWriter()
: 등록한 사용자는, 조회를 위해서 자신이 사용한 작성자와 비밀번호를 첨부해야 한다.
이때 자신이 등록한 구매 제안만 확인이 가능하다.
ProposalService.findPagedOffer()
: 페이지 기능을 지원한다.
3️⃣ [PUT] /items/{itemId}/proposals/{proposalId}
1. 구매 제안 작성자의 가격 수정
ProposalController.updateProposal()
, ProposalService.putUpdateOffer()
: 등록된 제안은 수정이 가능하다.
PasswordValidatable.validatePassword()
, ProposalEntity - @Override
: 이때, 제안이 등록될때 추가한 작성자와 비밀번호를 첨부해야 한다.
2. 물품 등록자의 구매 제안 수락, 거절 상태 변경
ProposalService.{putUpdateOffer(), acceptRejectOffer()}
: 대상 물품의 주인은 구매 제안을 수락할 수 있다.
또한, 대상 물품의 주인은 구매 제안을 거절할 수 있다. 각각 구매 제안의 상태는 수락/거절이 된다.
PasswordValidatable.validatePassword()
, ProposalEntity - @Override
: 이때, 제안이 등록될때 추가한 작성자와 비밀번호를 첨부해야 한다.
3. 구매 제안 작성자의 구매 확정 상태 변경
ProposalService.putUpdateOffer()
- 2) 현재 "수락" 상태 & Request "확정" 상태 -> 판매 완료
부분
1) 구매 제안을 등록한 사용자는, 자신이 등록한 제안이 수락 상태일 경우, 구매 확정을 할 수 있다.
2) 이때 구매 제안의 상태는 확정 상태가 된다.
3) 구매 제안이 확정될 경우, 대상 물품의 상태는 판매 완료가 된다.
참고로 확정, 판매 완료 상태의 구매 제안과 게시물은 작성자일지라도 삭제하지 못한다.
ProposalService.putUpdateOffer()
작성자 확인 부분,PasswordValidatable.validatePassword()
, ProposalEntity - @Override
비밀번호 확인 부분
: 이를 위해서 제안을 등록할 때 사용한 작성자와 비밀번호를 첨부해야 한다.
4️⃣ [DELETE] /items/{itemId}/proposals/{proposalId}
ProposalController.delete()
, ProposalService.deleteOffer()
: 등록된 제안은 수정이 가능하다.
PasswordValidatable.validatePassword()
, ProposalEntity - @Override
: 이때, 제안이 등록될때 추가한 작성자와 비밀번호를 첨부해야 한다.
펼쳐 보기
✨ 2023-07-27~28: DAY 2 / 관계 설정
Milestones
: 2️⃣ DAY 2 / 관계 설정하기
Issues
: DAY 2 / 관계 설정하기 #6
Pull Requests
: 관계 설정 및 관계 변경으로 인한 코드 변경 #8
✨ 2023-07-28~31: DAY 3 / 기능 접근 권한 설정
Milestones
: 3️⃣ DAY 3/ 기능 접근 설정하기
Issues
: DAY 3 / 기능에 대한 접근 권한 설정 #7
Pull Requests
: 관계 설정 및 관계 변경으로 인한 코드 변경 #8
Commits
:
- Role 부여 후 Status(ADMIN, USER) 추가
feat: Role(status) 추가 -> enum으로 생성 ROLE_ADMIN
권한일 경우 프로젝트의 모든 기능 사용 가능확정
상태의 구매 제안 삭제 등 제한된 기능 사용 가능
feat: ROLE_ADMIN의 경우 모든 기능을 수행할 수 있게 수정
✨ 2023-07-31~: DAY 4 / UI 구현
Milestones
: 4️⃣ DAY 4/ UI 구현하기
Issues
:
TODO
: HTML 댓글 등록 데이터 전달, 구매 제안 부분 구현 중