Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 3차 데모데이 - 인증인가, 카테고리, 태그, 검색, 로깅 #304

Merged
merged 191 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
191 commits
Select commit Hold shift + click to select a range
7298405
feat(domain): Category 도메인 추가
HoeSeong123 Jul 30, 2024
865d93d
feat(domain): Tag 도메인 추가
HoeSeong123 Jul 30, 2024
de7d1ae
refactor(domain): Tag와 Category에 기본 키 생성 어노테이션 추가
HoeSeong123 Jul 30, 2024
b73064b
style(service): test 코드 개행 추가
HoeSeong123 Jul 30, 2024
988fff6
feat(category): category 추가 기능 구현
HoeSeong123 Jul 30, 2024
6b99ee7
feat(category): category 이름 검증 기능 구현
HoeSeong123 Jul 30, 2024
292555f
feat(category): category 이름 중복 검증 기능 구현
HoeSeong123 Jul 30, 2024
f23f7ae
feat(category): category 전체 조회 기능 구현
HoeSeong123 Jul 30, 2024
62de1ab
refactor(domain): lombok 어노테이션 순서 코드 구현 순서에 맞게 수정
HoeSeong123 Jul 30, 2024
aae4cae
refactor(domain): Tag value 필드를 name으로 변경
HoeSeong123 Jul 31, 2024
00070a2
feat(codezap): template 생성 기능에 tag와 category 추가
HoeSeong123 Jul 31, 2024
965fdb3
Merge remote-tracking branch 'upstream/dev/be' into feat/domain_update
HoeSeong123 Jul 31, 2024
36afbd7
Merge pull request #225 from HoeSeong123/feat/template_refactor
HoeSeong123 Jul 31, 2024
7a3ed8f
refactor(test): 테스트 코드 오류 수정
HoeSeong123 Jul 31, 2024
100d7ec
feat(test): 템플릿 조회 기능에 카테고리, 태그 추가 및 테스트 코드 수정
HoeSeong123 Jul 31, 2024
22bbb48
feat(template): 템플릿 수정 기능에 카테고리 및 태그 추가
HoeSeong123 Aug 1, 2024
d8d40bc
feat(template): 템플릿 삭제 기능 추가
HoeSeong123 Aug 1, 2024
a435e35
Merge pull request #231 from HoeSeong123/feat/template_refactor_chorong
zangsu Aug 1, 2024
457a856
docs(category): 카테고리 기능 swagger 문서화
zangsu Aug 1, 2024
f11917b
docs(category): 카테고리 업데이트 기능 구현
zangsu Aug 1, 2024
c4ff264
refactor(controller): 테스트 코드 네이밍 및 DisplayName 수정
zangsu Aug 1, 2024
13a537c
feat(category): 카테고리 삭제 기능 구현
zangsu Aug 1, 2024
fd8faf6
docs(category): 카테고리 문서화 업데이트
zangsu Aug 1, 2024
4ae7d2a
Merge pull request #240 from zangsu/feat/category
HoeSeong123 Aug 2, 2024
7122fd4
feat(domain): Category 도메인 추가
HoeSeong123 Jul 30, 2024
d13b256
feat(domain): Tag 도메인 추가
HoeSeong123 Jul 30, 2024
dd7fbb7
refactor(domain): Tag와 Category에 기본 키 생성 어노테이션 추가
HoeSeong123 Jul 30, 2024
7ebdb48
style(service): test 코드 개행 추가
HoeSeong123 Jul 30, 2024
4ee7928
feat(category): category 추가 기능 구현
HoeSeong123 Jul 30, 2024
786a089
feat(category): category 이름 검증 기능 구현
HoeSeong123 Jul 30, 2024
778f92a
feat(category): category 이름 중복 검증 기능 구현
HoeSeong123 Jul 30, 2024
d64dfa2
feat(category): category 전체 조회 기능 구현
HoeSeong123 Jul 30, 2024
05fe337
refactor(domain): lombok 어노테이션 순서 코드 구현 순서에 맞게 수정
HoeSeong123 Jul 30, 2024
2fc9651
refactor(domain): Tag value 필드를 name으로 변경
HoeSeong123 Jul 31, 2024
d12f797
feat(codezap): template 생성 기능에 tag와 category 추가
HoeSeong123 Jul 31, 2024
8df56e4
refactor(test): 테스트 코드 오류 수정
HoeSeong123 Jul 31, 2024
fae0611
feat(test): 템플릿 조회 기능에 카테고리, 태그 추가 및 테스트 코드 수정
HoeSeong123 Jul 31, 2024
a36eb9a
feat(template): 템플릿 수정 기능에 카테고리 및 태그 추가
HoeSeong123 Aug 1, 2024
7c6ff87
feat(template): 템플릿 삭제 기능 추가
HoeSeong123 Aug 1, 2024
d713868
docs(category): 카테고리 기능 swagger 문서화
zangsu Aug 1, 2024
1b8b6a1
docs(category): 카테고리 업데이트 기능 구현
zangsu Aug 1, 2024
03eb850
refactor(controller): 테스트 코드 네이밍 및 DisplayName 수정
zangsu Aug 1, 2024
ba02215
feat(category): 카테고리 삭제 기능 구현
zangsu Aug 1, 2024
ebab110
docs(category): 카테고리 문서화 업데이트
zangsu Aug 1, 2024
e0a45e3
Merge remote-tracking branch 'upstream/feat/domain_update' into feat/…
HoeSeong123 Aug 2, 2024
c087ac3
feat(template): 템플릿 dto에 description 필드 추가
HoeSeong123 Aug 1, 2024
5ecab42
feat(cors): cors 헤더 권한 추가
HoeSeong123 Aug 1, 2024
96e8aae
docs(codezap): template 및 category 문서화 업데이트
HoeSeong123 Aug 2, 2024
bc2fc49
refactor(template): dto 제약 조건 추가 및 테스트 코드 추가
HoeSeong123 Aug 2, 2024
901e450
style(codezap): 코드 스타일 정리
HoeSeong123 Aug 2, 2024
cc8db54
refactor(codezap): 코드 전반적인 리팩토링
HoeSeong123 Aug 2, 2024
24781a9
feat(codezap): Valid 순서 설정 기능 구현
HoeSeong123 Aug 2, 2024
c6b77e7
feat(service): 기본 카테고리 삭제 시 예외 처리
HoeSeong123 Aug 2, 2024
d851e92
Merge pull request #249 from HoeSeong123/feat/template_refactor_chorong
HoeSeong123 Aug 2, 2024
94679a9
test(member): 회원가입 서비스 테스트
zeus6768 Aug 1, 2024
112e0a9
feat(member): 회원가입 구현
zeus6768 Aug 1, 2024
70efacf
refactor(member): 메서드명 가독성 개선
zeus6768 Aug 1, 2024
6d3f0d9
docs(member): 회원가입 API 명세
zeus6768 Aug 1, 2024
abd0fd1
test(service): 실패하는 로그인 서비스 테스트
zeus6768 Aug 2, 2024
3bb4841
refactor(service): 불필요한 주석 제거
zeus6768 Aug 2, 2024
8d94d14
refactor(service): 사용하지 않는 location 헤더 삭제
zeus6768 Aug 2, 2024
430c233
refactor(repository): 메서드 순서 변경
zeus6768 Aug 3, 2024
6ff7488
test(service): 로그인 및 쿠키 인증 테스트
zeus6768 Aug 3, 2024
db9d551
feat(member): 로그인
zeus6768 Aug 3, 2024
3cc80df
refactor(repository): 메서드 순서 변경
zeus6768 Aug 4, 2024
03a68c2
refactor(repository): 서비스와 컨트롤러 역할 분리
zeus6768 Aug 4, 2024
090bdce
feat(configuration): 쿠키 인증 argument resolver 등록
zeus6768 Aug 4, 2024
9dc6fae
refactor(service): 테스트 가독성 개선
zeus6768 Aug 4, 2024
2ee0a62
refactor(service): 회원가입 메서드가 결과를 반환하도록 변경
zeus6768 Aug 4, 2024
a2fc045
refactor(service): repository 구조 변경
zeus6768 Aug 4, 2024
e18434b
refactor(service): 쿠키 인증 관련 서비스 분리 및 메서드 변경
zeus6768 Aug 4, 2024
cfec856
docs(member): API 명세 작성
zeus6768 Aug 5, 2024
98d2da8
feat(controller): 로그아웃
zeus6768 Aug 5, 2024
970a3f8
test(service): 불필요한 주석 삭제
zeus6768 Aug 5, 2024
145f9a8
feat(service): 스니펫 순서 검증 groups 추가
HoeSeong123 Aug 4, 2024
e59816e
refactor(validation): 검증 순서 변경
HoeSeong123 Aug 4, 2024
dbd5900
feat(codezap): 빈 값에 대한 검증 구현
HoeSeong123 Aug 4, 2024
b267eea
feat(codezap): 스니펫이 비어있는 경우에 대한 검증 구현
HoeSeong123 Aug 4, 2024
094b5b7
refactor(category): 코드 가독성 향상을 위한 리팩토링
HoeSeong123 Aug 5, 2024
b8e1995
refactor(codezap): responseDto 네이밍 변경
HoeSeong123 Aug 5, 2024
04356e0
refactor(codezap): NoArgsConstructor 접근 제어자 protected로 변경
HoeSeong123 Aug 5, 2024
3071716
refactor(category): 의미 있는 숫자 상수화 (기본 카테고리의 id)
HoeSeong123 Aug 5, 2024
96408ed
refactor(template): 중복 메서드 삭제
HoeSeong123 Aug 5, 2024
4165b92
refactor(codezap): ValidationGroups에서 겹치는 내용 통합
HoeSeong123 Aug 5, 2024
e30fb4b
refactor(configuration): argument resolver의 의존성 변경
zeus6768 Aug 5, 2024
629e0b6
fix(error): @ApiErrorResponse 반복 정의를 위한 컨테이너 어노테이션 생성
jminkkk Aug 5, 2024
ec7f582
fix(error): 중복 정의 가능하도록 @Repeatable 추가
jminkkk Aug 5, 2024
8d6afea
refactor(service): tag와 tagName 변수명 구분
HoeSeong123 Aug 5, 2024
9ace4c4
refactor(member): 이메일, 사용자명 확인 api의 응답 변경
zeus6768 Aug 5, 2024
c117e0e
refactor(service): 메서드 위치 변경
HoeSeong123 Aug 5, 2024
eb14c4a
fix(service): 중복 검사
zeus6768 Aug 5, 2024
99b6fd9
refactor(service): 태그 생성 및 수정 로직 변경
HoeSeong123 Aug 5, 2024
3ad7717
test(service): 응답 변경에 따른 리팩토링
zeus6768 Aug 5, 2024
80a57d0
feat(logger): 요청 로깅 시 쿠키 값도 함께 기록
jminkkk Aug 5, 2024
d7c7481
refactor(controller): 불필요한 개행 제거
HoeSeong123 Aug 5, 2024
d6bcbce
refactor(logger): Content를 인코딩하는 중복 코드 메서드로 추출
jminkkk Aug 5, 2024
eac479c
refactor(service): 상수 접근 제어자 private으로 변경
HoeSeong123 Aug 5, 2024
34c94c8
refactor(domain): 상수명 변경
HoeSeong123 Aug 5, 2024
4cbfb71
refactor(response): 불필요한 개행 제거
HoeSeong123 Aug 5, 2024
8b1b520
refactor(codezap): 템플릿 생성 메서드명 변경
HoeSeong123 Aug 5, 2024
39336ab
refactor(service): stream을 통한 save 대신 saveAll을 사용하도록 변경
HoeSeong123 Aug 5, 2024
72d3fa2
docs: 템플릿 토픽 검색 API 문서화
kyum-q Jul 24, 2024
1ad7b14
feat(service): 템플릿 제목에 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
733a2c8
feat(service): 탬플릿의 스니펫 파일명 중 하나라도 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
9a045ff
feat(service): 탬플릿의 스니펫 코드 중 하나라도 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
aece2ca
feat(controller): 템플릿 토픽 검색 성공
kyum-q Jul 24, 2024
c069d12
test(service): nested를 이용한 검색 테스트 통합
kyum-q Jul 24, 2024
d125035
refactor(service): 조건문을 별도의 메서드로 분리
HoeSeong123 Aug 5, 2024
1b7a7e6
refactor(service): 가독성 증가를 위해 코드 분리
kyum-q Jul 24, 2024
ab771f7
fix(controller): requestParam 연결 문제 해결 및 테스트 수정
kyum-q Aug 5, 2024
dcdec66
test(service): 도메인 변경에 따른 테스트 오류 해결
kyum-q Aug 5, 2024
b6b9b32
test(controller): @Nested로 테스트 분리
kyum-q Aug 5, 2024
aa70382
feat(service): 탬플릿 설명에 토픽이 포함된 경우 검색 결과에 포함
kyum-q Aug 5, 2024
88718ca
fix(logger): MethodExecutionTimeAspect의 포인트컷 표현식 변경
jminkkk Aug 5, 2024
05cd1b4
refactor(template): findBy 메서드의 리턴값을 Optional로 변경
HoeSeong123 Aug 6, 2024
b83da74
refactor(resources): 제품 코드와 테스트 코드 설정 분리
zeus6768 Aug 5, 2024
0dba693
Merge pull request #252 from woowacourse-teams/feat/domain_update
HoeSeong123 Aug 6, 2024
b8463e6
fix(template): 템플릿 수정 로직 버그 수정
HoeSeong123 Aug 6, 2024
d661424
fix(template): 템플릿 수정 로직 버그 수정
HoeSeong123 Aug 6, 2024
3206f1d
fix(template): 템플릿 수정시 올바른 요청을 확인하는 로직 수정
HoeSeong123 Aug 6, 2024
6f6347c
refactor(service): 쿠키 값 검증 로직 추가
zeus6768 Aug 6, 2024
669bf84
fix(configuration): 애너테이션 활성화
zeus6768 Aug 6, 2024
411ab7e
refactor(service): 불필요한 코드 제거
HoeSeong123 Aug 6, 2024
1a8bbf1
docs(dto): `@NotEmpty` -> `@NotBlank`
zeus6768 Aug 6, 2024
da0fcd9
refactor(domain): 짱수 리뷰 반영 - 변수명 변경
zeus6768 Aug 6, 2024
d6630d1
Merge pull request #286 from HoeSeong123/fix/test_code
jminkkk Aug 6, 2024
27c381a
Merge pull request #270 from jminkkk/fix/error_swagger_response
HoeSeong123 Aug 6, 2024
ae74ea0
fix(dto): 특수문자를 포함하지 않아도 되도록 변경
zeus6768 Aug 6, 2024
0858470
refactor(template): 기존의 템플릿 탐색 API 와 템플릿 검색 API 를 구분하기 위해 기존의 템플릿 탐색 …
zangsu Aug 5, 2024
fe6d046
docs(templates): 요구사항 변경에 따른 API 명세 변경
zangsu Aug 6, 2024
0184b17
feat(templates): 카테고리, 태그 이름으로 템플릿을 필터링 하는 기능 구현
zangsu Aug 5, 2024
c85fe13
refactor(template): 코드 리팩토링
zangsu Aug 6, 2024
825d7c4
refactor(template): 목록 조회 응답에 전체 템플릿 개수 필드 추가
zangsu Aug 6, 2024
dfedaf6
docs(response): 응답 문서화
zangsu Aug 6, 2024
1fb61ee
fix(service): 쿠키 null 검증 추가
zeus6768 Aug 6, 2024
8ad2ec6
Merge pull request #289 from zangsu/feat/findAll
zangsu Aug 6, 2024
65e3fb8
Merge pull request #278 from jminkkk/feat/logging
jminkkk Aug 6, 2024
5feb1ec
Merge pull request #283 from jminkkk/fix/method_logger_not_work
jminkkk Aug 6, 2024
a97311e
fix(configuration): cors 설정 추가
zeus6768 Aug 6, 2024
ec5034b
fix merge conflicts
zeus6768 Aug 6, 2024
eaa0c4c
fix(cors): cors 설정 추가
zeus6768 Aug 6, 2024
ff5276b
Merge pull request #267 from zeus6768/feat/member
zeus6768 Aug 6, 2024
e911be9
merge conflict 해결
HoeSeong123 Aug 6, 2024
65f9498
feat(category): 카테고리 생성에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
d339330
feat(category): 카테고리 조회에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
1171b34
feat(category): 카테고리 수정에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
e284c75
feat(domain): 기본 카테고리인지 확인하는 필드 추가
HoeSeong123 Aug 6, 2024
c9ae6b6
feat(domain): 카테고리 삭제에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
0a1bc77
feat(template): 템플릿 생성에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
3185cb0
refactor(category): 예외 처리 메세지 변경
HoeSeong123 Aug 6, 2024
a70e583
feat(template): 템플릿 상세 조회에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
d312134
feat(template): 템플릿 수정에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
b3b7c37
feat(template): 템플릿 삭제에 멤버 정보 추가
HoeSeong123 Aug 6, 2024
bd2a49a
feat(member): 회원가입시 기본 카테고리 생성되도록 구현
HoeSeong123 Aug 6, 2024
30f8b12
docs: 템플릿 토픽 검색 API 문서화
kyum-q Jul 24, 2024
1baefbb
feat(service): 템플릿 제목에 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
7847326
feat(service): 탬플릿의 스니펫 파일명 중 하나라도 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
b9a68a1
feat(service): 탬플릿의 스니펫 코드 중 하나라도 토픽이 포함된 경우 검색 결과에 포함
kyum-q Jul 24, 2024
104be9f
feat(controller): 템플릿 토픽 검색 성공
kyum-q Jul 24, 2024
23f36a3
test(service): nested를 이용한 검색 테스트 통합
kyum-q Jul 24, 2024
5daca4c
refactor(service): 가독성 증가를 위해 코드 분리
kyum-q Jul 24, 2024
cf45783
fix(controller): requestParam 연결 문제 해결 및 테스트 수정
kyum-q Aug 5, 2024
8718ac1
test(service): 도메인 변경에 따른 테스트 오류 해결
kyum-q Aug 5, 2024
4c97324
test(controller): @Nested로 테스트 분리
kyum-q Aug 5, 2024
b6bca4e
feat(service): 탬플릿 설명에 토픽이 포함된 경우 검색 결과에 포함
kyum-q Aug 5, 2024
2ae9201
feat(response): 내 템플릿 검색에서 사용되는 response 구현
kyum-q Aug 6, 2024
d15687c
feat(template): 템플릿 검색 response 변경
kyum-q Aug 6, 2024
0d82a20
feat(template): 페이징 추가
kyum-q Aug 6, 2024
aae4012
fix(template): rebase 후에 충돌난 코드 해결
kyum-q Aug 6, 2024
2d11e21
feat(template): 프론트엔드와 필드 명, 파라미터 명 동기화
zangsu Aug 6, 2024
b649b08
Merge pull request #291 from zangsu/feat/findAll
zangsu Aug 6, 2024
808084d
feat(domain): 카테고리 유니크 설정 추가
HoeSeong123 Aug 6, 2024
ed072d1
test(codezap): 실패하는 테스트 코드 수정
HoeSeong123 Aug 6, 2024
108d37d
test(service): 권한이 없는 접근에 대한 상태 코드 수정
HoeSeong123 Aug 6, 2024
2b022e1
fix(file): fileNamePattern에 날짜 포맷이 적용되지 않음 해결
jminkkk Aug 6, 2024
019958b
refactor(resources): 로그 파일 위치 property로 변경
jminkkk Aug 6, 2024
a0e06fa
Merge pull request #295 from jminkkk/fix/set_logback
jminkkk Aug 6, 2024
0b7a944
feat(template): 필드 명 변경에 따른 메서드 명 변경
zangsu Aug 6, 2024
ab5e34e
Merge pull request #299 from zangsu/feat/findAll
zangsu Aug 6, 2024
9992fc0
chore: 테스트 sql문 수정
HoeSeong123 Aug 6, 2024
ef91b53
Merge branch 'dev/be' into feat/crud_with_member
HoeSeong123 Aug 6, 2024
a31e243
fix(service): 실패하는 테스트 코드 수정
HoeSeong123 Aug 6, 2024
edc188e
Merge pull request #298 from HoeSeong123/feat/crud_with_member
HoeSeong123 Aug 6, 2024
f69ce38
feat(template): 검색 로직 멤버 연결
kyum-q Aug 6, 2024
0b5f7fd
Merge branch 'feat/search2' of https://github.com/kyum-q/2024-code-za…
kyum-q Aug 6, 2024
3fb3707
Merge branch 'dev/be' into feat/search2
kyum-q Aug 6, 2024
8e9318e
refactor(logger): swagger 요청 시 로깅 제외
jminkkk Aug 6, 2024
4d6de9e
refactor(logger): 응답, 요청 시 모든 헤더 로깅
jminkkk Aug 6, 2024
93f290f
Merge pull request #303 from jminkkk/refactor/request_log_add_header
jminkkk Aug 6, 2024
70568c2
fix: merge conflict
kyum-q Aug 6, 2024
c29c24f
Merge pull request #280 from kyum-q/feat/search2
kyum-q Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package codezap.category.controller;

import java.net.URI;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import codezap.category.dto.request.CreateCategoryRequest;
import codezap.category.dto.request.UpdateCategoryRequest;
import codezap.category.dto.response.FindAllCategoriesResponse;
import codezap.category.service.CategoryService;
import codezap.global.validation.ValidationSequence;
import codezap.member.configuration.BasicAuthentication;
import codezap.member.dto.MemberDto;

@RestController
@RequestMapping("/categories")
public class CategoryController implements SpringDocCategoryController {

private final CategoryService categoryService;

public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}

@PostMapping
public ResponseEntity<Void> createCategory(
@Validated(ValidationSequence.class) @RequestBody CreateCategoryRequest createCategoryRequest,
@BasicAuthentication MemberDto memberDto
) {
Long createdCategoryId = categoryService.create(createCategoryRequest, memberDto);
return ResponseEntity.created(URI.create("/categories/" + createdCategoryId))
.build();
}

@GetMapping
public ResponseEntity<FindAllCategoriesResponse> getCategories(@BasicAuthentication MemberDto memberDto) {
return ResponseEntity.ok(categoryService.findAllByMember(memberDto));
}

@PutMapping("/{id}")
public ResponseEntity<Void> updateCategory(
@PathVariable Long id,
@Validated(ValidationSequence.class) @RequestBody UpdateCategoryRequest updateCategoryRequest,
@BasicAuthentication MemberDto memberDto
) {
categoryService.update(id, updateCategoryRequest, memberDto);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteCategory(@PathVariable Long id, @BasicAuthentication MemberDto memberDto) {
categoryService.deleteById(id, memberDto);
return ResponseEntity.noContent()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package codezap.category.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import codezap.category.dto.request.CreateCategoryRequest;
import codezap.category.dto.request.UpdateCategoryRequest;
import codezap.category.dto.response.FindAllCategoriesResponse;
import codezap.global.swagger.error.ApiErrorResponse;
import codezap.global.swagger.error.ErrorCase;
import codezap.member.dto.MemberDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "카테고리 CRUD API", description = "카테고리 생성, 목록 조회, 삭제, 수정 API")
public interface SpringDocCategoryController {

@Operation(summary = "카테고리 생성", description = """
새로운 카테고리를 생성합니다. \n
새로운 카테고리의 이름이 필요합니다. \n
""")
@ApiResponse(responseCode = "201", description = "카테고리 생성 성공", headers = {
@Header(name = "생성된 카테고리의 API 경로", example = "/categories/1")})
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = {
@ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."),
@ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다."),
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.")
})
ResponseEntity<Void> createCategory(CreateCategoryRequest createCategoryRequest, MemberDto memberDto);

@Operation(summary = "카테고리 목록 조회", description = "생성된 모든 카테고리를 조회합니다.")
@ApiResponse(responseCode = "200", description = "조회 성공",
content = {@Content(schema = @Schema(implementation = FindAllCategoriesResponse.class))})
ResponseEntity<FindAllCategoriesResponse> getCategories(MemberDto memberDto);

@Operation(summary = "카테고리 수정", description = "해당하는 식별자의 카테고리를 수정합니다.")
@ApiResponse(responseCode = "200", description = "카테고리 수정 성공")
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = {
@ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우",
exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."),
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우",
exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.")
})
ResponseEntity<Void> updateCategory(Long id, UpdateCategoryRequest updateCategoryRequest, MemberDto memberDto);

@Operation(summary = "카테고리 삭제", description = "해당하는 식별자의 카테고리를 삭제합니다.")
@ApiResponse(responseCode = "204", description = "카테고리 삭제 성공")
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = {
@ErrorCase(description = "삭제하려는 카테고리에 템플릿이 존재하는 경우",
exampleMessage = "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."),
})
ResponseEntity<Void> deleteCategory(Long id, MemberDto memberDto);
}
62 changes: 62 additions & 0 deletions backend/src/main/java/codezap/category/domain/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package codezap.category.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import codezap.global.auditing.BaseTimeEntity;
import codezap.member.domain.Member;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Table(
uniqueConstraints={
@UniqueConstraint(
name="name_with_member",
columnNames={"member_id", "name"}
)
}
)
public class Category extends BaseTimeEntity {

private static final String DEFAULT_CATEGORY_NAME = "카테고리 없음";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Member member;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private Boolean isDefault;

public Category(String name, Member member) {
this.name = name;
this.member = member;
this.isDefault = false;
}

public static Category createDefaultCategory(Member member) {
return new Category(null, member, DEFAULT_CATEGORY_NAME, true);
}

public void updateName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package codezap.category.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import codezap.global.validation.ValidationGroups.NotNullGroup;
import codezap.global.validation.ValidationGroups.SizeCheckGroup;
import io.swagger.v3.oas.annotations.media.Schema;

public record CreateCategoryRequest(
@Schema(description = "카테고리 이름", example = "Spring")
@NotBlank(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class)
@Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class)
String name
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package codezap.category.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import codezap.global.validation.ValidationGroups.NotNullGroup;
import codezap.global.validation.ValidationGroups.SizeCheckGroup;
import io.swagger.v3.oas.annotations.media.Schema;

public record UpdateCategoryRequest(
@Schema(description = "카테고리 이름", example = "Spring")
@NotBlank(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class)
@Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class)
String name
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package codezap.category.dto.response;

import java.util.List;

import codezap.category.domain.Category;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindAllCategoriesResponse(
@Schema(description = "카테고리 목록")
List<FindCategoryResponse> categories
) {
public static FindAllCategoriesResponse from(List<Category> categories) {
return new FindAllCategoriesResponse(
categories.stream()
.map(FindCategoryResponse::from)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package codezap.category.dto.response;

import codezap.category.domain.Category;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindCategoryResponse(
@Schema(description = "카테고리 식별자", example = "1")
Long id,
@Schema(description = "카테고리 이름", example = "Spring")
String name
) {
public static FindCategoryResponse from(Category category) {
return new FindCategoryResponse(category.getId(), category.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package codezap.category.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;

import codezap.category.domain.Category;
import codezap.global.exception.CodeZapException;
import codezap.member.domain.Member;

@SuppressWarnings("unused")
public interface CategoryJpaRepository extends CategoryRepository, JpaRepository<Category, Long> {

default Category fetchById(Long id) {
return findById(id).orElseThrow(
() -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
}

List<Category> findAllByMember(Member member);

boolean existsByNameAndMember(String categoryName, Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package codezap.category.repository;

import java.util.List;

import codezap.category.domain.Category;
import codezap.member.domain.Member;

public interface CategoryRepository {

Category fetchById(Long id);

List<Category> findAllByMember(Member member);

List<Category> findAll();

boolean existsByNameAndMember(String categoryName, Member member);

Category save(Category category);

void deleteById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package codezap.category.service;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import codezap.category.domain.Category;
import codezap.category.dto.request.CreateCategoryRequest;
import codezap.category.dto.request.UpdateCategoryRequest;
import codezap.category.dto.response.FindAllCategoriesResponse;
import codezap.category.repository.CategoryRepository;
import codezap.global.exception.CodeZapException;
import codezap.member.domain.Member;
import codezap.member.dto.MemberDto;
import codezap.member.repository.MemberJpaRepository;
import codezap.member.repository.MemberRepository;
import codezap.template.repository.TemplateRepository;

@Service
public class CategoryService {

private final CategoryRepository categoryRepository;
private final TemplateRepository templateRepository;
private final MemberRepository memberJpaRepository;

public CategoryService(CategoryRepository categoryRepository, TemplateRepository templateRepository,
MemberJpaRepository memberJpaRepository
) {
this.categoryRepository = categoryRepository;
this.templateRepository = templateRepository;
this.memberJpaRepository = memberJpaRepository;
}

@Transactional
public Long create(CreateCategoryRequest createCategoryRequest, MemberDto memberDto) {
String categoryName = createCategoryRequest.name();
Member member = memberJpaRepository.fetchById(memberDto.id());
validateDuplicatedCategory(categoryName, member);
Category category = new Category(categoryName, member);
return categoryRepository.save(category).getId();
}

public FindAllCategoriesResponse findAllByMember(MemberDto memberDto) {
Member member = memberJpaRepository.fetchById(memberDto.id());
return FindAllCategoriesResponse.from(categoryRepository.findAllByMember(member));
}

public FindAllCategoriesResponse findAll() {
return FindAllCategoriesResponse.from(categoryRepository.findAll());
}

@Transactional
public void update(Long id, UpdateCategoryRequest updateCategoryRequest, MemberDto memberDto) {
Member member = memberJpaRepository.fetchById(memberDto.id());
validateDuplicatedCategory(updateCategoryRequest.name(), member);
Category category = categoryRepository.fetchById(id);
validateAuthorizeMember(category, member);
category.updateName(updateCategoryRequest.name());
}

private void validateDuplicatedCategory(String categoryName, Member member) {
if (categoryRepository.existsByNameAndMember(categoryName, member)) {
throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다.");
}
}

public void deleteById(Long id, MemberDto memberDto) {
Member member = memberJpaRepository.fetchById(memberDto.id());
Category category = categoryRepository.fetchById(id);
validateAuthorizeMember(category, member);

if (templateRepository.existsByCategoryId(id)) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다.");
}
if (category.getIsDefault()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "기본 카테고리는 삭제할 수 없습니다.");
}
categoryRepository.deleteById(id);
}

private void validateAuthorizeMember(Category category, Member member) {
if (!category.getMember().equals(member)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "해당 카테고리를 수정 또는 삭제할 권한이 없는 유저입니다.");
}
}
}
Loading