From c7349f5f2b321e9a547ca43e85a532f2cdcd7098 Mon Sep 17 00:00:00 2001 From: JisooPark27 <32264819+JisooPark27@users.noreply.github.com> Date: Sun, 25 Jul 2021 22:51:38 +0900 Subject: [PATCH 01/46] [ADD] actuator --- build.gradle | 11 ++++- .../youtubedb/YoutubeDbApplication.java | 2 + .../example/youtubedb/domain/Playlist.java | 1 + src/main/resources/application.properties | 6 --- src/main/resources/application.yaml | 48 +++++++++++++++++++ src/main/resources/import.sql | 2 +- 6 files changed, 61 insertions(+), 9 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/build.gradle b/build.gradle index 67f40f8..85f52f0 100644 --- a/build.gradle +++ b/build.gradle @@ -19,12 +19,19 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'com.h2database:h2' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'de.codecentric:spring-boot-admin-starter-server:2.4.1' + implementation 'de.codecentric:spring-boot-admin-starter-client:2.4.1' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index 4d909bf..673669e 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -1,9 +1,11 @@ package com.example.youtubedb; +import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@EnableAdminServer public class YoutubeDbApplication { public static void main(String[] args) { diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index eabedac..f981d2f 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -16,6 +16,7 @@ public class Playlist { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String title; private boolean isPublic; private long likeCnt = 0; @Enumerated(value = EnumType.STRING) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 093cd5c..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -spring.h2.console.enabled = true -spring.datasource.url = jdbc:h2:mem:testdb -spring.datasource.driver-class-name = org.h2.Driver -spring.datasource.username = sa -spring.jpa.show-sql = true -spring.jpa.hibernate.ddl-auto = create \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..511bd53 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,48 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:h2:tcp://localhost/~/spring + username: sa + password: + driver-class-name: org.h2.Driver + + boot: + admin: + client: + url: http://localhost:8080 + + + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + show_sql: true + format_sql: true + +logging: + level: + org.hibernate.SQL: debug + + +management: + endpoints: + web: + exposure: + include: "*" + # include: health, metrics + + base-path: "/monitoring" + + endpoint: + health: + # enabled: true + # show-details: always + group: + custom: + include: diskSpace, ping + show-components: always + show-details: always diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index bb1031e..64386b0 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,3 +1,3 @@ insert into MEMBER values(1, '|0_0|', 'oooo@gmailc.com'); insert into PLAYLIST values(1, 'GAME', true, 0, 1); -insert into PLAY values(1, 1000, NOW(), 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file +insert into PLAY values(1, 1000, NOW(), 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file From e5dca1c1f09541a36f6e8df377ce8fda2dc129a1 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 26 Jul 2021 00:09:17 +0900 Subject: [PATCH 02/46] [ADD] Actuator --- build.gradle | 2 ++ .../com/example/youtubedb/domain/Playlist.java | 4 +++- src/main/resources/application.properties | 16 ++++++++++++++-- src/main/resources/import.sql | 4 ++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 67f40f8..2b21641 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'de.codecentric:spring-boot-admin-starter-client:2.3.1' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index eabedac..4a86079 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -16,6 +16,7 @@ public class Playlist { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String title; private boolean isPublic; private long likeCnt = 0; @Enumerated(value = EnumType.STRING) @@ -27,7 +28,8 @@ public class Playlist { private List plays = new ArrayList<>(); @Builder - public Playlist(boolean isPublic, Category category, Member member) { + public Playlist(String title, boolean isPublic, Category category, Member member) { + this.title = title; this.isPublic = isPublic; this.category = category; this.member = member; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 093cd5c..e6bdc7a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,18 @@ +# h2 spring.h2.console.enabled = true spring.datasource.url = jdbc:h2:mem:testdb spring.datasource.driver-class-name = org.h2.Driver spring.datasource.username = sa -spring.jpa.show-sql = true -spring.jpa.hibernate.ddl-auto = create \ No newline at end of file +spring.jpa.show-sql = true +spring.jpa.hibernate.ddl-auto = create + +# actuator +#management.endpoints.web.exposure.include = health, metrics, shutdown +#management.endpoint.shutdown.enabled = true +management.endpoints.web.base-path = /monitoring +management.endpoints.web.exposure.include = * +management.endpoint.health.show-details = always +management.server.port = 8005 + +# admin ui +spring.boot.admin.client.url = http://localhost:8090 diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index bb1031e..38a57f7 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,3 +1,3 @@ insert into MEMBER values(1, '|0_0|', 'oooo@gmailc.com'); -insert into PLAYLIST values(1, 'GAME', true, 0, 1); -insert into PLAY values(1, 1000, NOW(), 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file +insert into PLAYLIST values(1, 'GAME', true, 0, 'myList', 1); +insert into PLAY values(1, 1000, NOW(), 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file From ecaad604935d775870ed583d287818065258f7c7 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 26 Jul 2021 00:18:36 +0900 Subject: [PATCH 03/46] [Merge] Actuator --- build.gradle | 11 +++-- .../youtubedb/YoutubeDbApplication.java | 2 + src/main/resources/application.properties | 18 ------- src/main/resources/application.yaml | 49 +++++++++++++++++++ 4 files changed, 59 insertions(+), 21 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/build.gradle b/build.gradle index 2b21641..a673115 100644 --- a/build.gradle +++ b/build.gradle @@ -19,14 +19,19 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'de.codecentric:spring-boot-admin-starter-client:2.3.1' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'de.codecentric:spring-boot-admin-starter-server:2.4.1' + implementation 'de.codecentric:spring-boot-admin-starter-client:2.4.1' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index 4d909bf..673669e 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -1,9 +1,11 @@ package com.example.youtubedb; +import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@EnableAdminServer public class YoutubeDbApplication { public static void main(String[] args) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index e6bdc7a..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,18 +0,0 @@ -# h2 -spring.h2.console.enabled = true -spring.datasource.url = jdbc:h2:mem:testdb -spring.datasource.driver-class-name = org.h2.Driver -spring.datasource.username = sa -spring.jpa.show-sql = true -spring.jpa.hibernate.ddl-auto = create - -# actuator -#management.endpoints.web.exposure.include = health, metrics, shutdown -#management.endpoint.shutdown.enabled = true -management.endpoints.web.base-path = /monitoring -management.endpoints.web.exposure.include = * -management.endpoint.health.show-details = always -management.server.port = 8005 - -# admin ui -spring.boot.admin.client.url = http://localhost:8090 diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..d023149 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,49 @@ +server: + port: 8080 + +spring: + datasource: +# url: jdbc:h2:tcp://localhost/~/spring + url: jdbc:h2:mem:testdb + username: sa + password: + driver-class-name: org.h2.Driver + + boot: + admin: + client: + url: http://localhost:8080 + + + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + show_sql: true + format_sql: true + +logging: + level: + org.hibernate.SQL: debug + + +management: + endpoints: + web: + exposure: + include: "*" + # include: health, metrics + + base-path: "/monitoring" + + endpoint: + health: + # enabled: true + # show-details: always + group: + custom: + include: diskSpace, ping + show-components: always + show-details: always From fe80f29ef8d3eb4182dc3b36c407c3c57872f8c2 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 26 Jul 2021 13:19:27 +0900 Subject: [PATCH 04/46] =?UTF-8?q?[ADD]=20Member=20DB=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20&=20=EA=B0=80=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84(=EB=B9=84=ED=9A=8C=EC=9B=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberController.java | 39 +++++++++++++++ .../com/example/youtubedb/domain/Member.java | 14 ++++-- .../com/example/youtubedb/dto/ErrorDto.java | 11 ++++ .../example/youtubedb/dto/ResponseDto.java | 12 +++++ .../exception/ControllerExceptionHandler.java | 48 ++++++++++++++++++ .../exception/DuplicateMemberException.java | 7 +++ .../NotExistRequestValueException.java | 7 +++ .../repository/MemberRepository.java | 12 +++++ .../SpringDataJpaMemberRepository.java | 7 +++ .../youtubedb/service/MemberService.java | 44 ++++++++++++++++ .../example/youtubedb/util/RequestUtil.java | 13 +++++ src/main/resources/import.sql | 2 +- .../service/MemberServiceIntegrationTest.java | 50 +++++++++++++++++++ .../youtubedb/util/RequestUtilTest.java | 31 ++++++++++++ 14 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/controller/MemberController.java create mode 100644 src/main/java/com/example/youtubedb/dto/ErrorDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/ResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java create mode 100644 src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java create mode 100644 src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java create mode 100644 src/main/java/com/example/youtubedb/repository/MemberRepository.java create mode 100644 src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java create mode 100644 src/main/java/com/example/youtubedb/service/MemberService.java create mode 100644 src/main/java/com/example/youtubedb/util/RequestUtil.java create mode 100644 src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java create mode 100644 src/test/java/com/example/youtubedb/util/RequestUtilTest.java diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java new file mode 100644 index 0000000..18f22c2 --- /dev/null +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -0,0 +1,39 @@ +package com.example.youtubedb.controller; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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 java.util.Map; + +@RestController +@RequestMapping("/api/member") +public class MemberController { + + private final MemberService memberService; + + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + @PostMapping("/register/non") + public ResponseEntity registerNonMember(@RequestBody Map request) { + String deviceId = request.get("deviceId"); + Member nonMember = memberService.registerNon(deviceId); + + ResponseDto responseBody = ResponseDto.builder() + .success(true) + .response(nonMember) + .error(null) + .build(); + + return ResponseEntity.ok(responseBody); + } +} diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/Member.java index 8608d1a..1eed42d 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/Member.java @@ -1,5 +1,6 @@ package com.example.youtubedb.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -15,14 +16,17 @@ public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String email; - private String displayName; + private String loginId; + @JsonIgnore + private String password; + private boolean isMember; @OneToMany(mappedBy = "member") private List playlists = new ArrayList<>(); @Builder - public Member(String email, String displayName) { - this.email = email; - this.displayName = displayName; + public Member(String loginId, String password, boolean isMember) { + this.loginId = loginId; + this.password = password; + this.isMember = isMember; } } diff --git a/src/main/java/com/example/youtubedb/dto/ErrorDto.java b/src/main/java/com/example/youtubedb/dto/ErrorDto.java new file mode 100644 index 0000000..a6f0592 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/ErrorDto.java @@ -0,0 +1,11 @@ +package com.example.youtubedb.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ErrorDto { + private final String message; + private final int status; +} diff --git a/src/main/java/com/example/youtubedb/dto/ResponseDto.java b/src/main/java/com/example/youtubedb/dto/ResponseDto.java new file mode 100644 index 0000000..e4ff9e4 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/ResponseDto.java @@ -0,0 +1,12 @@ +package com.example.youtubedb.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ResponseDto { + private final boolean success; + private final Object response; + private final ErrorDto error; +} diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java new file mode 100644 index 0000000..a37af9f --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -0,0 +1,48 @@ +package com.example.youtubedb.exception; + +import com.example.youtubedb.dto.ErrorDto; +import com.example.youtubedb.dto.ResponseDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ControllerExceptionHandler { + @ExceptionHandler({ + NotExistRequestValueException.class, + DuplicateMemberException.class + }) + public ResponseEntity badRequest(Exception e) { + ErrorDto errorDto = ErrorDto.builder() + .message(e.getMessage()) + .status(HttpStatus.BAD_REQUEST.value()) + .build(); + + ResponseDto responseDto = ResponseDto.builder() + .success(false) + .response(null) + .error(errorDto) + .build(); + + return ResponseEntity.badRequest().body(responseDto); + } + + @ExceptionHandler({ + RuntimeException.class + }) + public ResponseEntity serverError(Exception e) { + ErrorDto errorDto = ErrorDto.builder() + .message(e.getMessage()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build(); + + ResponseDto responseDto = ResponseDto.builder() + .success(false) + .response(null) + .error(errorDto) + .build(); + + return ResponseEntity.internalServerError().body(responseDto); + } +} diff --git a/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java b/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java new file mode 100644 index 0000000..80e52da --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class DuplicateMemberException extends RuntimeException { + public DuplicateMemberException() { + super("ID가 중복된 회원입니다."); + } +} diff --git a/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java b/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java new file mode 100644 index 0000000..caeb74e --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class NotExistRequestValueException extends RuntimeException { + public NotExistRequestValueException() { + super("필요값이 없습니다."); + } +} diff --git a/src/main/java/com/example/youtubedb/repository/MemberRepository.java b/src/main/java/com/example/youtubedb/repository/MemberRepository.java new file mode 100644 index 0000000..2b22249 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/MemberRepository.java @@ -0,0 +1,12 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Member; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository { + Member save(Member member); + Optional findByLoginId(String loginId); + List findAll(); +} diff --git a/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java b/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java new file mode 100644 index 0000000..2df4529 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SpringDataJpaMemberRepository extends JpaRepository, MemberRepository { +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java new file mode 100644 index 0000000..3934f8f --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -0,0 +1,44 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.exception.DuplicateMemberException; +import com.example.youtubedb.repository.MemberRepository; +import com.example.youtubedb.util.RequestUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional +public class MemberService { + private final MemberRepository memberRepository; + + @Autowired + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + + public Member registerNon(String deviceId) { + List requestList = new ArrayList<>(); + requestList.add(deviceId); + RequestUtil.checkNeedValue(requestList); + + checkDuplicateMember(deviceId); + + Member nonMember = Member.builder() + .isMember(false) + .loginId(deviceId) + .password(null) + .build(); + + return memberRepository.save(nonMember); + } + + private void checkDuplicateMember(String deviceId) { + memberRepository.findByLoginId(deviceId).ifPresent(m -> { throw new DuplicateMemberException(); }); + } +} diff --git a/src/main/java/com/example/youtubedb/util/RequestUtil.java b/src/main/java/com/example/youtubedb/util/RequestUtil.java new file mode 100644 index 0000000..1c01b29 --- /dev/null +++ b/src/main/java/com/example/youtubedb/util/RequestUtil.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.util; + +import com.example.youtubedb.exception.NotExistRequestValueException; + +import java.util.List; + +public class RequestUtil { + public static void checkNeedValue(List requestList) { + requestList.forEach(r -> { + if(r == null) throw new NotExistRequestValueException(); + }); + } +} diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 38a57f7..8298f52 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,3 +1,3 @@ -insert into MEMBER values(1, '|0_0|', 'oooo@gmailc.com'); +insert into MEMBER values(1, false, 'helloMan', 'hello123'); insert into PLAYLIST values(1, 'GAME', true, 0, 'myList', 1); insert into PLAY values(1, 1000, NOW(), 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java new file mode 100644 index 0000000..a931145 --- /dev/null +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -0,0 +1,50 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.exception.DuplicateMemberException; +import com.example.youtubedb.repository.MemberRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +class MemberServiceIntegrationTest { + @Autowired + MemberRepository memberRepository; + @Autowired + MemberService memberService; + + @Test + void 비회원_등록() { + // given + String deviceId = "device001"; + + // when + Member nonMember = memberService.registerNon(deviceId); + + // then + assertAll( + () -> assertThat(nonMember.isMember()).isEqualTo(false), + () -> assertThat(nonMember.getLoginId()).isEqualTo(deviceId), + () -> assertThat(nonMember.getPassword()).isEqualTo(null) + ); + } + + @Test + void 등록시_중복() { + // given + String deviceId = "device001"; + + // when + memberService.registerNon(deviceId); + Exception e = assertThrows(DuplicateMemberException.class, () -> memberService.registerNon(deviceId)); + + // then + assertThat(e.getMessage()).isEqualTo("ID가 중복된 회원입니다."); + } +} \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java new file mode 100644 index 0000000..44f911e --- /dev/null +++ b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java @@ -0,0 +1,31 @@ +package com.example.youtubedb.util; + +import com.example.youtubedb.exception.NotExistRequestValueException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class RequestUtilTest { + + @Test + void 요청값_존재X() { + // given + String param1 = "params"; + Integer param2 = 3; + String params3 = null; + List requestList = new ArrayList<>(); + requestList.add(param1); + requestList.add(param2); + requestList.add(params3); + + // when + Exception e = assertThrows(NotExistRequestValueException.class, () -> RequestUtil.checkNeedValue(requestList)); + + // then + assertThat(e.getMessage()).isEqualTo("필요값이 없습니다."); + } +} \ No newline at end of file From a46081f53df5a44b168ee79436dafe36ec8494dc Mon Sep 17 00:00:00 2001 From: oh980225 Date: Tue, 27 Jul 2021 17:14:03 +0900 Subject: [PATCH 05/46] =?UTF-8?q?[ADD]=20=ED=94=8C=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1,=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=88=98=EC=A0=95=20=EA=B5=AC=ED=98=84=20&=20Memb?= =?UTF-8?q?er=EC=9D=98=20playList=20=EC=A1=B0=ED=9A=8C=20&=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=A0=81=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/YoutubeDbApplication.java | 2 + .../controller/MemberController.java | 13 +- .../controller/PlaylistController.java | 75 ++++++++++ .../example/youtubedb/domain/BaseEntity.java | 23 +++ .../com/example/youtubedb/domain/Member.java | 11 +- .../com/example/youtubedb/domain/Play.java | 14 +- .../example/youtubedb/domain/Playlist.java | 18 ++- .../exception/ControllerExceptionHandler.java | 36 ++--- .../exception/InvalidAccessException.java | 7 + .../exception/NotExistMemberException.java | 7 + .../exception/NotExistPlaylistException.java | 7 + .../repository/MemberRepository.java | 1 - .../repository/PlaylistRepository.java | 13 ++ .../SpringDataJpaPlaylistRepository.java | 7 + .../youtubedb/service/MemberService.java | 14 +- .../youtubedb/service/PlaylistService.java | 78 +++++++++++ .../example/youtubedb/util/RequestUtil.java | 8 +- .../example/youtubedb/util/ResponseUtil.java | 49 +++++++ src/main/resources/import.sql | 6 +- .../service/MemberServiceIntegrationTest.java | 36 ++++- .../PlaylistServiceIntegrationTest.java | 132 ++++++++++++++++++ .../youtubedb/util/RequestUtilTest.java | 13 +- .../youtubedb/util/ResponseUtilTest.java | 76 ++++++++++ 23 files changed, 571 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/controller/PlaylistController.java create mode 100644 src/main/java/com/example/youtubedb/domain/BaseEntity.java create mode 100644 src/main/java/com/example/youtubedb/exception/InvalidAccessException.java create mode 100644 src/main/java/com/example/youtubedb/exception/NotExistMemberException.java create mode 100644 src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java create mode 100644 src/main/java/com/example/youtubedb/repository/PlaylistRepository.java create mode 100644 src/main/java/com/example/youtubedb/repository/SpringDataJpaPlaylistRepository.java create mode 100644 src/main/java/com/example/youtubedb/service/PlaylistService.java create mode 100644 src/main/java/com/example/youtubedb/util/ResponseUtil.java create mode 100644 src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java create mode 100644 src/test/java/com/example/youtubedb/util/ResponseUtilTest.java diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index 673669e..b4e2386 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -3,9 +3,11 @@ import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableAdminServer +@EnableJpaAuditing public class YoutubeDbApplication { public static void main(String[] args) { diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 18f22c2..0dd5817 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -3,6 +3,8 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.ResponseUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -17,22 +19,21 @@ public class MemberController { private final MemberService memberService; + private final PlaylistService playlistService; @Autowired - public MemberController(MemberService memberService) { + public MemberController(MemberService memberService, PlaylistService playlistService) { this.memberService = memberService; + this.playlistService = playlistService; } @PostMapping("/register/non") public ResponseEntity registerNonMember(@RequestBody Map request) { String deviceId = request.get("deviceId"); Member nonMember = memberService.registerNon(deviceId); + playlistService.createPlaylist("default", "false", "OTHER", nonMember); - ResponseDto responseBody = ResponseDto.builder() - .success(true) - .response(nonMember) - .error(null) - .build(); + ResponseDto responseBody = ResponseUtil.getSuccessResponse(nonMember); return ResponseEntity.ok(responseBody); } diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java new file mode 100644 index 0000000..e3baa60 --- /dev/null +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -0,0 +1,75 @@ +package com.example.youtubedb.controller; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.ResponseUtil; +import com.fasterxml.jackson.databind.util.JSONWrappedObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Spring Security로 JWT 적용시 변경 필요 +@RestController +@RequestMapping("/api/playlist") +public class PlaylistController { + private final PlaylistService playlistService; + private final MemberService memberService; + + @Autowired + public PlaylistController(PlaylistService playlistService, MemberService memberService) { + this.playlistService = playlistService; + this.memberService = memberService; + } + + @GetMapping("/{loginId}") + public ResponseEntity getPlaylist(@PathVariable("loginId") String loginId) { + Member member = memberService.findMemberByLoginId(loginId); + List playlists = member.getPlaylists(); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(playlists); + + return ResponseEntity.ok(responseBody); + } + + @PostMapping("/create") + public ResponseEntity createPlaylist(@RequestBody Map request) { + Member member = memberService.findMemberByLoginId(request.get("loginId")); + Playlist playlist = playlistService.createPlaylist( + request.get("title"), + request.get("public"), + request.get("category"), + member); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(playlist); + + return ResponseEntity.ok(responseBody); + } + + @PutMapping("/edit") + public ResponseEntity editPlaylist(@RequestBody Map request) { + Member member = memberService.findMemberByLoginId(request.get("loginId")); + playlistService.editPlaylistTitle(request.get("id"), request.get("title"), member); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getEditResponse(request.get("id"))); + + return ResponseEntity.ok(responseBody); + } + + @DeleteMapping("/delete") + public ResponseEntity deletePlaylist(@RequestBody Map request) { + Member member = memberService.findMemberByLoginId(request.get("loginId")); + playlistService.deletePlaylistById(request.get("id"), member); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getDeleteResponse(request.get("id"))); + + return ResponseEntity.ok(responseBody); + } + +} diff --git a/src/main/java/com/example/youtubedb/domain/BaseEntity.java b/src/main/java/com/example/youtubedb/domain/BaseEntity.java new file mode 100644 index 0000000..75d47f4 --- /dev/null +++ b/src/main/java/com/example/youtubedb/domain/BaseEntity.java @@ -0,0 +1,23 @@ +package com.example.youtubedb.domain; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@MappedSuperclass +@Getter +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/Member.java index 1eed42d..cf124d5 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/Member.java @@ -12,15 +12,16 @@ @Entity @Getter @NoArgsConstructor -public class Member { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Member extends BaseEntity { +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; private String loginId; @JsonIgnore private String password; private boolean isMember; - @OneToMany(mappedBy = "member") + @JsonIgnore + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List playlists = new ArrayList<>(); @Builder diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index e2a3a72..f16daa4 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -10,17 +10,17 @@ @Entity @Getter @NoArgsConstructor -public class Play { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Play extends BaseEntity { +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; private String videoId; private long start; private long end; private String thumbnail; private String title; private int sequence; - private LocalDate publishedAt; + private String channelAvatar; @ManyToOne @JoinColumn(name = "playlist_id") private Playlist playlist; @@ -31,15 +31,13 @@ public Play(String videoId, long end, String thumbnail, String title, - int sequence, - Playlist playlist) { + int sequence) { this.videoId = videoId; this.start = start; this.end = end; this.thumbnail = thumbnail; this.title = title; this.sequence = sequence; - this.playlist = playlist; } public void setPlaylist(Playlist playlist) { diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index 4a86079..bcf5b22 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -1,9 +1,11 @@ package com.example.youtubedb.domain; import com.example.youtubedb.dto.Category; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import javax.persistence.*; import java.util.ArrayList; @@ -12,10 +14,11 @@ @Entity @Getter @NoArgsConstructor -public class Playlist { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Playlist extends BaseEntity { +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; + @Setter private String title; private boolean isPublic; private long likeCnt = 0; @@ -23,16 +26,17 @@ public class Playlist { private Category category; @ManyToOne @JoinColumn(name = "member_id") + @JsonIgnore private Member member; - @OneToMany(mappedBy = "playlist") + @JsonIgnore + @OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL) private List plays = new ArrayList<>(); @Builder - public Playlist(String title, boolean isPublic, Category category, Member member) { + public Playlist(String title, boolean isPublic, Category category) { this.title = title; this.isPublic = isPublic; this.category = category; - this.member = member; } public void setMember(Member member) { diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index a37af9f..51f2d29 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -2,6 +2,7 @@ import com.example.youtubedb.dto.ErrorDto; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.util.ResponseUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -11,38 +12,29 @@ public class ControllerExceptionHandler { @ExceptionHandler({ NotExistRequestValueException.class, - DuplicateMemberException.class + DuplicateMemberException.class, + NotExistMemberException.class, + NotExistPlaylistException.class, + InvalidAccessException.class }) public ResponseEntity badRequest(Exception e) { - ErrorDto errorDto = ErrorDto.builder() - .message(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build(); + ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()); - ResponseDto responseDto = ResponseDto.builder() - .success(false) - .response(null) - .error(errorDto) - .build(); + return ResponseEntity.badRequest().body(responseBody); + } + + public ResponseEntity notAcceptable(Exception e) { + ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.NOT_ACCEPTABLE.value()); - return ResponseEntity.badRequest().body(responseDto); + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(responseBody); } @ExceptionHandler({ RuntimeException.class }) public ResponseEntity serverError(Exception e) { - ErrorDto errorDto = ErrorDto.builder() - .message(e.getMessage()) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build(); - - ResponseDto responseDto = ResponseDto.builder() - .success(false) - .response(null) - .error(errorDto) - .build(); + ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value()); - return ResponseEntity.internalServerError().body(responseDto); + return ResponseEntity.internalServerError().body(responseBody); } } diff --git a/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java b/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java new file mode 100644 index 0000000..ae1ee72 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class InvalidAccessException extends RuntimeException { + public InvalidAccessException() { + super("올바르지 못한 접근입니다."); + } +} diff --git a/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java b/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java new file mode 100644 index 0000000..1638684 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class NotExistMemberException extends RuntimeException { + public NotExistMemberException() { + super("존재하지 않는 회원입니다."); + } +} diff --git a/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java b/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java new file mode 100644 index 0000000..458106a --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class NotExistPlaylistException extends RuntimeException { + public NotExistPlaylistException() { + super("존재하지 않는 플레이리스트입니다."); + } +} diff --git a/src/main/java/com/example/youtubedb/repository/MemberRepository.java b/src/main/java/com/example/youtubedb/repository/MemberRepository.java index 2b22249..b79b0d7 100644 --- a/src/main/java/com/example/youtubedb/repository/MemberRepository.java +++ b/src/main/java/com/example/youtubedb/repository/MemberRepository.java @@ -8,5 +8,4 @@ public interface MemberRepository { Member save(Member member); Optional findByLoginId(String loginId); - List findAll(); } diff --git a/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java b/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java new file mode 100644 index 0000000..70a6cef --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Playlist; + +import java.util.List; +import java.util.Optional; + +public interface PlaylistRepository { + Playlist save(Playlist playlist); + Optional findById(Long id); + List findAll(); + void deleteById(Long id); +} diff --git a/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlaylistRepository.java b/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlaylistRepository.java new file mode 100644 index 0000000..a6270b4 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlaylistRepository.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Playlist; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SpringDataJpaPlaylistRepository extends JpaRepository, PlaylistRepository { +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 3934f8f..b755900 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -2,6 +2,7 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.exception.DuplicateMemberException; +import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; import com.example.youtubedb.util.RequestUtil; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +11,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service @Transactional @@ -23,12 +25,8 @@ public MemberService(MemberRepository memberRepository) { public Member registerNon(String deviceId) { - List requestList = new ArrayList<>(); - requestList.add(deviceId); - RequestUtil.checkNeedValue(requestList); - + RequestUtil.checkNeedValue(deviceId); checkDuplicateMember(deviceId); - Member nonMember = Member.builder() .isMember(false) .loginId(deviceId) @@ -41,4 +39,10 @@ public Member registerNon(String deviceId) { private void checkDuplicateMember(String deviceId) { memberRepository.findByLoginId(deviceId).ifPresent(m -> { throw new DuplicateMemberException(); }); } + + public Member findMemberByLoginId(String loginId) { + Optional member = memberRepository.findByLoginId(loginId); + member.orElseThrow(NotExistMemberException::new); + return member.get(); + } } diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java new file mode 100644 index 0000000..57e275f --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -0,0 +1,78 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.Category; +import com.example.youtubedb.exception.InvalidAccessException; +import com.example.youtubedb.exception.NotExistPlaylistException; +import com.example.youtubedb.exception.NotExistRequestValueException; +import com.example.youtubedb.repository.PlaylistRepository; +import com.example.youtubedb.util.RequestUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +public class PlaylistService { + private final PlaylistRepository playlistRepository; + + @Autowired + public PlaylistService(PlaylistRepository playlistRepository) { + this.playlistRepository = playlistRepository; + } + + public Playlist createPlaylist(String title, String isPublic, String category, Member member) { + RequestUtil.checkNeedValue(title, isPublic, category, member); + checkCategory(category); + Playlist playlist = Playlist.builder() + .title(title) + .isPublic(Boolean.parseBoolean(isPublic)) + .category(Category.valueOf(category)) + .build(); + playlist.setMember(member); + + return playlistRepository.save(playlist); + } + + private void checkCategory(String category) { + Category[] categories = Category.values(); + int count = 0; + if (Arrays.stream(categories).noneMatch(c -> c.toString().equals(category))) { + throw new NotExistRequestValueException(); + } + } + + public Playlist editPlaylistTitle(String id, String title, Member member) { + RequestUtil.checkNeedValue(id, title); + Long lId = Long.parseLong(id); + Playlist playlist = checkExistPlaylist(lId); + checkOwn(playlist, member); + playlist.setTitle(title); + + return playlist; + } + + public void deletePlaylistById(String id, Member member) { + RequestUtil.checkNeedValue(id, member); + Long lId = Long.parseLong(id); + Playlist playlist = checkExistPlaylist(lId); + checkOwn(playlist, member); + playlistRepository.deleteById(lId); + } + + private Playlist checkExistPlaylist(Long lId) { + return playlistRepository.findById(lId).orElseThrow(NotExistPlaylistException::new); + } + + private void checkOwn(Playlist playlist, Member member) { + if (playlist.getMember() != member) { + throw new InvalidAccessException(); + } + } +} diff --git a/src/main/java/com/example/youtubedb/util/RequestUtil.java b/src/main/java/com/example/youtubedb/util/RequestUtil.java index 1c01b29..9f89a81 100644 --- a/src/main/java/com/example/youtubedb/util/RequestUtil.java +++ b/src/main/java/com/example/youtubedb/util/RequestUtil.java @@ -5,9 +5,9 @@ import java.util.List; public class RequestUtil { - public static void checkNeedValue(List requestList) { - requestList.forEach(r -> { - if(r == null) throw new NotExistRequestValueException(); - }); + public static void checkNeedValue(Object ...args) { + for(Object arg : args) { + if(arg == null) throw new NotExistRequestValueException(); + } } } diff --git a/src/main/java/com/example/youtubedb/util/ResponseUtil.java b/src/main/java/com/example/youtubedb/util/ResponseUtil.java new file mode 100644 index 0000000..47a6453 --- /dev/null +++ b/src/main/java/com/example/youtubedb/util/ResponseUtil.java @@ -0,0 +1,49 @@ +package com.example.youtubedb.util; + +import com.example.youtubedb.dto.ErrorDto; +import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.exception.NotExistRequestValueException; + +import java.util.HashMap; +import java.util.Map; + +public class ResponseUtil { + public static ResponseDto getSuccessResponse(Object data) { + return ResponseDto.builder() + .success(true) + .response(data) + .error(null) + .build(); + } + + public static ResponseDto getFailResponse(String message, int status) { + ErrorDto error = getErrorDto(message, status); + + return ResponseDto.builder() + .success(false) + .response(null) + .error(error) + .build(); + } + + private static ErrorDto getErrorDto(String message, int status) { + return ErrorDto.builder() + .status(status) + .message(message) + .build(); + } + + public static Map getDeleteResponse(String id) { + Map result = new HashMap<>(); + result.put("id", id); + result.put("deleted", true); + return result; + } + + public static Map getEditResponse(String id) { + Map result = new HashMap<>(); + result.put("id", id); + result.put("edited", true); + return result; + } +} diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 8298f52..56518c3 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,3 +1,3 @@ -insert into MEMBER values(1, false, 'helloMan', 'hello123'); -insert into PLAYLIST values(1, 'GAME', true, 0, 'myList', 1); -insert into PLAY values(1, 1000, NOW(), 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file +insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); +insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); +insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index a931145..040732b 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -2,20 +2,19 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.exception.DuplicateMemberException; -import com.example.youtubedb.repository.MemberRepository; +import com.example.youtubedb.exception.NotExistMemberException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest @Transactional class MemberServiceIntegrationTest { - @Autowired - MemberRepository memberRepository; @Autowired MemberService memberService; @@ -47,4 +46,33 @@ class MemberServiceIntegrationTest { // then assertThat(e.getMessage()).isEqualTo("ID가 중복된 회원입니다."); } + + @Test + void loginId_조회() { + // given + String deviceId = "device001"; + Member member = memberService.registerNon(deviceId); + + // when + Member result = memberService.findMemberByLoginId(member.getLoginId()); + + // then + assertAll( + () -> assertThat(result.isMember()).isEqualTo(false), + () -> assertThat(result.getLoginId()).isEqualTo(deviceId), + () -> assertThat(result.getPassword()).isEqualTo(null) + ); + } + + @Test + void loginId_조회_존재X() { + // given + String deviceId = "device001"; + + // when + Exception e = assertThrows(NotExistMemberException.class, () -> memberService.findMemberByLoginId(deviceId)); + + // then + assertThat(e.getMessage()).isEqualTo("존재하지 않는 회원입니다."); + } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java new file mode 100644 index 0000000..d24d24f --- /dev/null +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -0,0 +1,132 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.Category; +import com.example.youtubedb.exception.InvalidAccessException; +import com.example.youtubedb.exception.NotExistPlaylistException; +import com.example.youtubedb.exception.NotExistRequestValueException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +class PlaylistServiceIntegrationTest { + @Autowired + PlaylistService playlistService; + @Autowired + MemberService memberService; + + @Test + void 플레이리스트_생성() { + // given + Member member = memberService.registerNon("device001"); + String title = "myList"; + String isPublic = "true"; + String category = "GAME"; + + // when + Playlist playlist = playlistService.createPlaylist(title, isPublic, category, member); + + // then + assertAll( + () -> assertThat(playlist.getTitle()).isEqualTo(title), + () -> assertThat(playlist.getCategory()).isEqualTo(Category.GAME), + () -> assertThat(playlist.isPublic()).isEqualTo(true), + () -> assertThat(playlist.getMember()).isEqualTo(member), + () -> assertThat(playlist.getLikeCnt()).isEqualTo(0) + ); + } + + @Test + void 플레이리스트_생성_Category이상() { + // given + Member member = memberService.registerNon("device001"); + String title = "myList"; + String isPublic = "true"; + String category = "???"; + + // when + Exception e = assertThrows(NotExistRequestValueException.class, () -> playlistService.createPlaylist(title, isPublic, category, member)); + + // then + assertThat(e.getMessage()).isEqualTo("필요값이 없습니다."); + } + + @Test + void 플레이리스트_조회() { + // given + Member member = memberService.registerNon("devide001"); + playlistService.createPlaylist("myList", "false", "GAME", member); + playlistService.createPlaylist("myList2", "false", "OTHER", member); + + // when + List playlists = member.getPlaylists(); + + // then + assertThat(playlists.size()).isEqualTo(2); + } + + @Test + void 플레이리스트_수정() { + // given + Member member = memberService.registerNon("devide001"); + Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + String newTitle = "newList"; + // when + Playlist newList = playlistService.editPlaylistTitle(playlist.getId().toString(), newTitle, member); + + // then + assertThat(newList.getTitle()).isEqualTo(newTitle); + } + + @Test + void 플레이리스트_존재X() { + // given + Member member = memberService.registerNon("devide001"); + Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + String newTitle = "newList"; + // when + Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.editPlaylistTitle("100", newTitle, member)); + + // then + assertThat(e.getMessage()).isEqualTo("존재하지 않는 플레이리스트입니다."); + } + + @Test + void 플레이리스트_삭제() { + // given + Member member = memberService.registerNon("devide001"); + Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + + // when + playlistService.deletePlaylistById(playlist.getId().toString(), member); + Exception e = assertThrows(NotExistPlaylistException.class, + () -> playlistService.editPlaylistTitle(playlist.getId().toString(), "newTitle", member) + ); + + // then + assertThat(e.getMessage()).isEqualTo("존재하지 않는 플레이리스트입니다."); + } + + @Test + void 타유저_접근() { + // given + Member member1 = memberService.registerNon("device001"); + Member member2 = memberService.registerNon("device002"); + Playlist playlist = playlistService.createPlaylist("title", "false", "OTHER", member1); + + // when + Exception e = assertThrows(InvalidAccessException.class, () -> playlistService.deletePlaylistById(playlist.getId().toString(), member2)); + + // then + assertThat(e.getMessage()).isEqualTo("올바르지 못한 접근입니다."); + } +} \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java index 44f911e..9e75398 100644 --- a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java +++ b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java @@ -3,11 +3,8 @@ import com.example.youtubedb.exception.NotExistRequestValueException; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; class RequestUtilTest { @@ -16,14 +13,10 @@ class RequestUtilTest { // given String param1 = "params"; Integer param2 = 3; - String params3 = null; - List requestList = new ArrayList<>(); - requestList.add(param1); - requestList.add(param2); - requestList.add(params3); + String param3 = null; // when - Exception e = assertThrows(NotExistRequestValueException.class, () -> RequestUtil.checkNeedValue(requestList)); + Exception e = assertThrows(NotExistRequestValueException.class, () -> RequestUtil.checkNeedValue(param1, param2, param3)); // then assertThat(e.getMessage()).isEqualTo("필요값이 없습니다."); diff --git a/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java b/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java new file mode 100644 index 0000000..f320c06 --- /dev/null +++ b/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java @@ -0,0 +1,76 @@ +package com.example.youtubedb.util; + +import com.example.youtubedb.dto.ResponseDto; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class ResponseUtilTest { + @Test + void 성공_응답() { + // given + String data = "DATA"; + + // when + ResponseDto responseBody = ResponseUtil.getSuccessResponse(data); + + // then + assertAll( + () -> assertThat(responseBody.isSuccess()).isEqualTo(true), + () -> assertThat(responseBody.getResponse()).isEqualTo(data), + () -> assertThat(responseBody.getError()).isEqualTo(null) + ); + } + + @Test + void 실패_응답() { + // given + String message = "오류입니다."; + int status = HttpStatus.INTERNAL_SERVER_ERROR.value(); + + // when + ResponseDto responseBody = ResponseUtil.getFailResponse(message, status); + + // then + assertAll( + () -> assertThat(responseBody.isSuccess()).isEqualTo(false), + () -> assertThat(responseBody.getResponse()).isEqualTo(null), + () -> assertThat(responseBody.getError().getMessage()).isEqualTo(message), + () -> assertThat(responseBody.getError().getStatus()).isEqualTo(status) + ); + } + + @Test + void 수정시_응답객체() { + // given + String id = "1"; + + // then + Map result = ResponseUtil.getEditResponse(id); + + // when + assertAll( + () -> assertThat(result.get("id")).isEqualTo(id), + () -> assertThat(result.get("edited")).isEqualTo(true) + ); + } + + @Test + void 삭제시_응답객체() { + // given + String id = "1"; + + // then + Map result = ResponseUtil.getDeleteResponse(id); + + // when + assertAll( + () -> assertThat(result.get("id")).isEqualTo(id), + () -> assertThat(result.get("deleted")).isEqualTo(true) + ); + } +} \ No newline at end of file From 8c8c6aa95b4a919c0279c3c9804976121a57ae74 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Wed, 28 Jul 2021 18:29:24 +0900 Subject: [PATCH 06/46] =?UTF-8?q?[ADD]=20=ED=94=8C=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=20=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberController.java | 3 + .../youtubedb/controller/PlayController.java | 78 ++++++++++ .../controller/PlaylistController.java | 16 ++ .../com/example/youtubedb/domain/Member.java | 3 - .../com/example/youtubedb/domain/Play.java | 9 +- .../example/youtubedb/domain/Playlist.java | 3 - .../exception/ControllerExceptionHandler.java | 5 +- .../exception/DuplicateMemberException.java | 8 +- .../exception/InvalidAccessException.java | 8 +- .../exception/NotExistMemberException.java | 8 +- .../exception/NotExistPlaylistException.java | 8 +- .../NotExistRequestValueException.java | 8 +- .../exception/StartAndEndTimeException.java | 12 ++ .../youtubedb/repository/PlayRepository.java | 8 + .../SpringDataJpaPlayRepository.java | 7 + .../youtubedb/service/MemberService.java | 1 - .../youtubedb/service/PlayService.java | 68 +++++++++ .../youtubedb/service/PlaylistService.java | 7 +- .../service/MemberServiceIntegrationTest.java | 4 +- .../service/PlayServiceIntegrationTest.java | 143 ++++++++++++++++++ .../PlaylistServiceIntegrationTest.java | 18 +-- .../youtubedb/util/RequestUtilTest.java | 2 +- 22 files changed, 393 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/controller/PlayController.java create mode 100644 src/main/java/com/example/youtubedb/exception/StartAndEndTimeException.java create mode 100644 src/main/java/com/example/youtubedb/repository/PlayRepository.java create mode 100644 src/main/java/com/example/youtubedb/repository/SpringDataJpaPlayRepository.java create mode 100644 src/main/java/com/example/youtubedb/service/PlayService.java create mode 100644 src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 0dd5817..b92220b 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -4,6 +4,7 @@ import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.RequestUtil; import com.example.youtubedb.util.ResponseUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -29,6 +30,8 @@ public MemberController(MemberService memberService, PlaylistService playlistSer @PostMapping("/register/non") public ResponseEntity registerNonMember(@RequestBody Map request) { + RequestUtil.checkNeedValue(request.get("deviceId")); + String deviceId = request.get("deviceId"); Member nonMember = memberService.registerNon(deviceId); playlistService.createPlaylist("default", "false", "OTHER", nonMember); diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java new file mode 100644 index 0000000..fe2adb4 --- /dev/null +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -0,0 +1,78 @@ +package com.example.youtubedb.controller; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Play; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.service.PlayService; +import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.RequestUtil; +import com.example.youtubedb.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +// play, playlist의 변경 작업 시 본인인지 확인하는 부분을 aop로 빼도 괜찮을 듯? + +@RestController +@RequestMapping("/api/play") +public class PlayController { + private final PlayService playService; + private final PlaylistService playlistService; + private final MemberService memberService; + + @Autowired + public PlayController(PlayService playService, PlaylistService playlistService, MemberService memberService) { + this.playService = playService; + this.playlistService = playlistService; + this.memberService = memberService; + } + + @GetMapping("/list") + public ResponseEntity getPlays(@RequestBody Map request) { + RequestUtil.checkNeedValue(request.get("loginId"), request.get("playlistId")); + + Member member = memberService.findMemberByLoginId(request.get("loginId")); + Playlist playlist = playlistService.checkExistPlaylist(Long.parseLong(request.get("playlistId"))); + List plays = playService.getPlaysInPlaylist(playlist, member); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(plays); + + return ResponseEntity.ok(responseBody); + } + + @PostMapping("/create") + public ResponseEntity createPlay(@RequestBody Map request) { + RequestUtil.checkNeedValue( + request.get("loginId"), + request.get("playlistId"), + request.get("videoId"), + request.get("start"), + request.get("end"), + request.get("thumbnail"), + request.get("title"), + request.get("channelAvatar")); + + Member member = memberService.findMemberByLoginId(request.get("loginId")); + Playlist playlist = playlistService.checkExistPlaylist(Long.parseLong(request.get("playlistId"))); + playlistService.checkOwn(playlist, member); + Play play = playService.addPlayToPlaylist( + playlist, + request.get("videoId"), + Long.parseLong(request.get("start")), + Long.parseLong(request.get("end")), + request.get("thumbnail"), + request.get("title"), + request.get("channelAvatar")); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(play); + + return ResponseEntity.ok(responseBody); + } + + @DeleteMapping("/delete") +} diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index e3baa60..9ce640d 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -5,6 +5,7 @@ import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.RequestUtil; import com.example.youtubedb.util.ResponseUtil; import com.fasterxml.jackson.databind.util.JSONWrappedObject; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +41,12 @@ public ResponseEntity getPlaylist(@PathVariable("loginId") String loginId) { @PostMapping("/create") public ResponseEntity createPlaylist(@RequestBody Map request) { + RequestUtil.checkNeedValue( + request.get("loginId"), + request.get("title"), + request.get("public"), + request.get("category")); + Member member = memberService.findMemberByLoginId(request.get("loginId")); Playlist playlist = playlistService.createPlaylist( request.get("title"), @@ -54,6 +61,11 @@ public ResponseEntity createPlaylist(@RequestBody Map request @PutMapping("/edit") public ResponseEntity editPlaylist(@RequestBody Map request) { + RequestUtil.checkNeedValue( + request.get("loginId"), + request.get("id"), + request.get("title")); + Member member = memberService.findMemberByLoginId(request.get("loginId")); playlistService.editPlaylistTitle(request.get("id"), request.get("title"), member); @@ -64,6 +76,10 @@ public ResponseEntity editPlaylist(@RequestBody Map request) @DeleteMapping("/delete") public ResponseEntity deletePlaylist(@RequestBody Map request) { + RequestUtil.checkNeedValue( + request.get("loginId"), + request.get("id")); + Member member = memberService.findMemberByLoginId(request.get("loginId")); playlistService.deletePlaylistById(request.get("id"), member); diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/Member.java index cf124d5..8c0f0fa 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/Member.java @@ -13,9 +13,6 @@ @Getter @NoArgsConstructor public class Member extends BaseEntity { -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// private Long id; private String loginId; @JsonIgnore private String password; diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index f16daa4..245fad7 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -1,5 +1,6 @@ package com.example.youtubedb.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,9 +12,6 @@ @Getter @NoArgsConstructor public class Play extends BaseEntity { -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// private Long id; private String videoId; private long start; private long end; @@ -22,6 +20,7 @@ public class Play extends BaseEntity { private int sequence; private String channelAvatar; @ManyToOne + @JsonIgnore @JoinColumn(name = "playlist_id") private Playlist playlist; @@ -31,13 +30,15 @@ public Play(String videoId, long end, String thumbnail, String title, - int sequence) { + int sequence, + String channelAvatar) { this.videoId = videoId; this.start = start; this.end = end; this.thumbnail = thumbnail; this.title = title; this.sequence = sequence; + this.channelAvatar = channelAvatar; } public void setPlaylist(Playlist playlist) { diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index bcf5b22..123cb32 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -15,9 +15,6 @@ @Getter @NoArgsConstructor public class Playlist extends BaseEntity { -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// private Long id; @Setter private String title; private boolean isPublic; diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 51f2d29..aad8ffa 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -15,7 +15,7 @@ public class ControllerExceptionHandler { DuplicateMemberException.class, NotExistMemberException.class, NotExistPlaylistException.class, - InvalidAccessException.class + StartAndEndTimeException.class }) public ResponseEntity badRequest(Exception e) { ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()); @@ -23,6 +23,9 @@ public ResponseEntity badRequest(Exception e) { return ResponseEntity.badRequest().body(responseBody); } + @ExceptionHandler({ + InvalidAccessException.class + }) public ResponseEntity notAcceptable(Exception e) { ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.NOT_ACCEPTABLE.value()); diff --git a/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java b/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java index 80e52da..853dd98 100644 --- a/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java +++ b/src/main/java/com/example/youtubedb/exception/DuplicateMemberException.java @@ -1,7 +1,13 @@ package com.example.youtubedb.exception; public class DuplicateMemberException extends RuntimeException { + private static final String MESSAGE = "ID가 중복된 회원입니다."; + public DuplicateMemberException() { - super("ID가 중복된 회원입니다."); + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; } } diff --git a/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java b/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java index ae1ee72..d3f89d0 100644 --- a/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java +++ b/src/main/java/com/example/youtubedb/exception/InvalidAccessException.java @@ -1,7 +1,13 @@ package com.example.youtubedb.exception; public class InvalidAccessException extends RuntimeException { + private static final String MESSAGE = "올바르지 못한 접근입니다."; + public InvalidAccessException() { - super("올바르지 못한 접근입니다."); + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; } } diff --git a/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java b/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java index 1638684..39b892e 100644 --- a/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java +++ b/src/main/java/com/example/youtubedb/exception/NotExistMemberException.java @@ -1,7 +1,13 @@ package com.example.youtubedb.exception; public class NotExistMemberException extends RuntimeException { + private static final String MESSAGE = "존재하지 않는 회원입니다."; + public NotExistMemberException() { - super("존재하지 않는 회원입니다."); + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; } } diff --git a/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java b/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java index 458106a..7f6517c 100644 --- a/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java +++ b/src/main/java/com/example/youtubedb/exception/NotExistPlaylistException.java @@ -1,7 +1,13 @@ package com.example.youtubedb.exception; public class NotExistPlaylistException extends RuntimeException { + private static final String MESSAGE = "존재하지 않는 플레이리스트입니다."; + public NotExistPlaylistException() { - super("존재하지 않는 플레이리스트입니다."); + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; } } diff --git a/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java b/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java index caeb74e..34c2f32 100644 --- a/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java +++ b/src/main/java/com/example/youtubedb/exception/NotExistRequestValueException.java @@ -1,7 +1,13 @@ package com.example.youtubedb.exception; public class NotExistRequestValueException extends RuntimeException { + private static final String MESSAGE = "필요값이 없습니다."; + public NotExistRequestValueException() { - super("필요값이 없습니다."); + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; } } diff --git a/src/main/java/com/example/youtubedb/exception/StartAndEndTimeException.java b/src/main/java/com/example/youtubedb/exception/StartAndEndTimeException.java new file mode 100644 index 0000000..dd9cff9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/StartAndEndTimeException.java @@ -0,0 +1,12 @@ +package com.example.youtubedb.exception; + +public class StartAndEndTimeException extends RuntimeException { + private static final String MESSAGE = "시간 설정이 바르지 않습니다."; + public StartAndEndTimeException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/repository/PlayRepository.java b/src/main/java/com/example/youtubedb/repository/PlayRepository.java new file mode 100644 index 0000000..2453526 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/PlayRepository.java @@ -0,0 +1,8 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Play; + +public interface PlayRepository { + Play save(Play play); + void deleteById(Long id); +} diff --git a/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlayRepository.java b/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlayRepository.java new file mode 100644 index 0000000..ac45383 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/SpringDataJpaPlayRepository.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.Play; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SpringDataJpaPlayRepository extends JpaRepository, PlayRepository { +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index b755900..472cb18 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -25,7 +25,6 @@ public MemberService(MemberRepository memberRepository) { public Member registerNon(String deviceId) { - RequestUtil.checkNeedValue(deviceId); checkDuplicateMember(deviceId); Member nonMember = Member.builder() .isMember(false) diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java new file mode 100644 index 0000000..cf70da9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -0,0 +1,68 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Play; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.exception.InvalidAccessException; +import com.example.youtubedb.exception.StartAndEndTimeException; +import com.example.youtubedb.repository.PlayRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +public class PlayService { + private final PlayRepository playRepository; + + @Autowired + public PlayService(PlayRepository playRepository) { + this.playRepository = playRepository; + } + + public Play addPlayToPlaylist( + Playlist playlist, + String videoId, + Long start, + Long end, + String thumbnail, + String title, + String channelAvatar) { + checkTime(start, end); + + int sequence = playlist.getPlays().size() + 1; + + Play play = Play.builder() + .title(title) + .videoId(videoId) + .thumbnail(thumbnail) + .sequence(sequence) + .start(start) + .end(end) + .channelAvatar(channelAvatar) + .build(); + play.setPlaylist(playlist); + + return play; + } + + private void checkTime(Long start, Long end) { + if ((start > end) || start < 0) { + throw new StartAndEndTimeException(); + } + } + + public List getPlaysInPlaylist(Playlist playlist, Member member) { + validateWatch(playlist, member); + + return playlist.getPlays(); + } + + private void validateWatch(Playlist playlist, Member member) { + if (!playlist.getMember().getId().equals(member.getId()) && !playlist.isPublic()) { + throw new InvalidAccessException(); + } + } +} diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 57e275f..f0d08c0 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -28,7 +28,6 @@ public PlaylistService(PlaylistRepository playlistRepository) { } public Playlist createPlaylist(String title, String isPublic, String category, Member member) { - RequestUtil.checkNeedValue(title, isPublic, category, member); checkCategory(category); Playlist playlist = Playlist.builder() .title(title) @@ -49,7 +48,6 @@ private void checkCategory(String category) { } public Playlist editPlaylistTitle(String id, String title, Member member) { - RequestUtil.checkNeedValue(id, title); Long lId = Long.parseLong(id); Playlist playlist = checkExistPlaylist(lId); checkOwn(playlist, member); @@ -59,18 +57,17 @@ public Playlist editPlaylistTitle(String id, String title, Member member) { } public void deletePlaylistById(String id, Member member) { - RequestUtil.checkNeedValue(id, member); Long lId = Long.parseLong(id); Playlist playlist = checkExistPlaylist(lId); checkOwn(playlist, member); playlistRepository.deleteById(lId); } - private Playlist checkExistPlaylist(Long lId) { + public Playlist checkExistPlaylist(Long lId) { return playlistRepository.findById(lId).orElseThrow(NotExistPlaylistException::new); } - private void checkOwn(Playlist playlist, Member member) { + public void checkOwn(Playlist playlist, Member member) { if (playlist.getMember() != member) { throw new InvalidAccessException(); } diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index 040732b..f09ff63 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -44,7 +44,7 @@ class MemberServiceIntegrationTest { Exception e = assertThrows(DuplicateMemberException.class, () -> memberService.registerNon(deviceId)); // then - assertThat(e.getMessage()).isEqualTo("ID가 중복된 회원입니다."); + assertThat(e.getMessage()).isEqualTo(DuplicateMemberException.getErrorMessage()); } @Test @@ -73,6 +73,6 @@ class MemberServiceIntegrationTest { Exception e = assertThrows(NotExistMemberException.class, () -> memberService.findMemberByLoginId(deviceId)); // then - assertThat(e.getMessage()).isEqualTo("존재하지 않는 회원입니다."); + assertThat(e.getMessage()).isEqualTo(NotExistMemberException.getErrorMessage()); } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java new file mode 100644 index 0000000..2118672 --- /dev/null +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -0,0 +1,143 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Play; +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.exception.InvalidAccessException; +import com.example.youtubedb.exception.StartAndEndTimeException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@Transactional +class PlayServiceIntegrationTest { + @Autowired + private PlayService playService; + @Autowired + private MemberService memberService; + @Autowired + private PlaylistService playlistService; + + private String videoId; + private long start; + private long end; + private String thumbnail; + private String title; + private String channelAvatar; + + @BeforeEach + void setup() { + this.videoId = "video001"; + this.start = 100; + this.end = 1000; + this.thumbnail = "썸네일"; + this.title = "영상1"; + this.channelAvatar = "아바타 이미지"; + } + + @Test + void 플레이_추가() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + + // when + Play play = playService.addPlayToPlaylist( + playlist, + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + // then + assertAll( + () -> assertThat(play.getTitle()).isEqualTo(title), + () -> assertThat(play.getVideoId()).isEqualTo(videoId), + () -> assertThat(play.getStart()).isEqualTo(start), + () -> assertThat(play.getEnd()).isEqualTo(end), + () -> assertThat(play.getThumbnail()).isEqualTo(thumbnail), + () -> assertThat(play.getSequence()).isEqualTo(1), + () -> assertThat(play.getChannelAvatar()).isEqualTo(channelAvatar), + () -> assertThat(play.getPlaylist().getId()).isEqualTo(playlist.getId()) + ); + } + + @Test + void 플레이_추가_시간예외() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + + // when + Exception e = assertThrows(StartAndEndTimeException.class , () -> + playService.addPlayToPlaylist( + playlist, + videoId, + 0L, + -5L, + thumbnail, + title, + channelAvatar) + ); + + // then + assertThat(e.getMessage()).isEqualTo(StartAndEndTimeException.getErrorMessage()); + } + + @Test + void 영상목록_조회() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + playService.addPlayToPlaylist( + playlist, + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + // when + List plays = playService.getPlaysInPlaylist(playlist, member); + + // then + assertAll( + () -> assertThat(plays.size()).isEqualTo(1), + () -> assertThat(plays.get(0).getTitle()).isEqualTo(title) + ); + } + + @Test + void 영상목록_조회_접근권한X() { + // given + Member member = memberService.registerNon("device001"); + Member other = memberService.registerNon("device002"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + playService.addPlayToPlaylist( + playlist, + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + // when + Exception e = assertThrows(InvalidAccessException.class, () -> playService.getPlaysInPlaylist(playlist, other)); + + // then + assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); + } +} diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index d24d24f..8bb6dca 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -14,7 +14,8 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest @Transactional @@ -57,7 +58,7 @@ class PlaylistServiceIntegrationTest { Exception e = assertThrows(NotExistRequestValueException.class, () -> playlistService.createPlaylist(title, isPublic, category, member)); // then - assertThat(e.getMessage()).isEqualTo("필요값이 없습니다."); + assertThat(e.getMessage()).isEqualTo(NotExistRequestValueException.getErrorMessage()); } @Test @@ -92,12 +93,11 @@ class PlaylistServiceIntegrationTest { // given Member member = memberService.registerNon("devide001"); Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); - String newTitle = "newList"; // when - Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.editPlaylistTitle("100", newTitle, member)); + Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.checkExistPlaylist(100L)); // then - assertThat(e.getMessage()).isEqualTo("존재하지 않는 플레이리스트입니다."); + assertThat(e.getMessage()).isEqualTo(NotExistPlaylistException.getErrorMessage()); } @Test @@ -109,11 +109,11 @@ class PlaylistServiceIntegrationTest { // when playlistService.deletePlaylistById(playlist.getId().toString(), member); Exception e = assertThrows(NotExistPlaylistException.class, - () -> playlistService.editPlaylistTitle(playlist.getId().toString(), "newTitle", member) + () -> playlistService.checkExistPlaylist(playlist.getId()) ); // then - assertThat(e.getMessage()).isEqualTo("존재하지 않는 플레이리스트입니다."); + assertThat(e.getMessage()).isEqualTo(NotExistPlaylistException.getErrorMessage()); } @Test @@ -124,9 +124,9 @@ class PlaylistServiceIntegrationTest { Playlist playlist = playlistService.createPlaylist("title", "false", "OTHER", member1); // when - Exception e = assertThrows(InvalidAccessException.class, () -> playlistService.deletePlaylistById(playlist.getId().toString(), member2)); + Exception e = assertThrows(InvalidAccessException.class, () -> playlistService.checkOwn(playlist, member2)); // then - assertThat(e.getMessage()).isEqualTo("올바르지 못한 접근입니다."); + assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java index 9e75398..4e440ac 100644 --- a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java +++ b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java @@ -19,6 +19,6 @@ class RequestUtilTest { Exception e = assertThrows(NotExistRequestValueException.class, () -> RequestUtil.checkNeedValue(param1, param2, param3)); // then - assertThat(e.getMessage()).isEqualTo("필요값이 없습니다."); + assertThat(e.getMessage()).isEqualTo(NotExistRequestValueException.getErrorMessage()); } } \ No newline at end of file From 006cd7850523f425f6c42fc9856fdfa287e0b8ab Mon Sep 17 00:00:00 2001 From: oh980225 Date: Thu, 29 Jul 2021 22:30:36 +0900 Subject: [PATCH 07/46] =?UTF-8?q?[ADD]=20=ED=94=8C=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20=EC=8B=9C=EA=B0=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?&=20=ED=94=8C=EB=A0=88=EC=9D=B4=20=EC=9E=AC=EC=83=9D=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../youtubedb/controller/PlayController.java | 68 +++++- .../controller/PlaylistController.java | 5 +- .../example/youtubedb/domain/BaseEntity.java | 1 + .../com/example/youtubedb/domain/Play.java | 12 +- .../dto/play/PlayEditSeqRequestDto.java | 23 ++ .../youtubedb/dto/play/PlaySeqDto.java | 34 +++ .../exception/ControllerExceptionHandler.java | 5 +- .../exception/DuplicateSeqException.java | 13 ++ .../exception/InvalidSeqException.java | 13 ++ .../exception/NotExistPlayException.java | 13 ++ .../youtubedb/repository/PlayRepository.java | 3 + .../youtubedb/service/PlayService.java | 64 +++++- .../youtubedb/service/PlaylistService.java | 25 +-- .../example/youtubedb/util/RequestUtil.java | 7 + .../example/youtubedb/util/ResponseUtil.java | 12 +- .../service/PlayServiceIntegrationTest.java | 198 +++++++++++++++++- .../PlaylistServiceIntegrationTest.java | 40 ++-- .../youtubedb/util/RequestUtilTest.java | 14 ++ .../youtubedb/util/ResponseUtilTest.java | 26 ++- 20 files changed, 512 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java create mode 100644 src/main/java/com/example/youtubedb/exception/DuplicateSeqException.java create mode 100644 src/main/java/com/example/youtubedb/exception/InvalidSeqException.java create mode 100644 src/main/java/com/example/youtubedb/exception/NotExistPlayException.java diff --git a/build.gradle b/build.gradle index a673115..88341e1 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,8 @@ dependencies { implementation 'de.codecentric:spring-boot-admin-starter-server:2.4.1' implementation 'de.codecentric:spring-boot-admin-starter-client:2.4.1' + implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index fe2adb4..a7cf8c2 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -1,14 +1,16 @@ package com.example.youtubedb.controller; -import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.play.PlayEditSeqRequestDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlayService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import com.example.youtubedb.util.ResponseUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -17,8 +19,10 @@ import java.util.Map; // play, playlist의 변경 작업 시 본인인지 확인하는 부분을 aop로 빼도 괜찮을 듯? +// 근데 세부사항이 좀 다를 수 있어서..우선 Util로 빼놓자! @RestController +@Tag(name = "영상 관련 API") @RequestMapping("/api/play") public class PlayController { private final PlayService playService; @@ -33,12 +37,12 @@ public PlayController(PlayService playService, PlaylistService playlistService, } @GetMapping("/list") + @Operation(description = "영상 목록 조회") public ResponseEntity getPlays(@RequestBody Map request) { RequestUtil.checkNeedValue(request.get("loginId"), request.get("playlistId")); - Member member = memberService.findMemberByLoginId(request.get("loginId")); - Playlist playlist = playlistService.checkExistPlaylist(Long.parseLong(request.get("playlistId"))); - List plays = playService.getPlaysInPlaylist(playlist, member); + Playlist playlist = playlistService.getPlaylistById(Long.parseLong(request.get("playlistId"))); + List plays = playService.getPlaysInPlaylist(playlist, request.get("loginId")); ResponseDto responseBody = ResponseUtil.getSuccessResponse(plays); @@ -46,6 +50,7 @@ public ResponseEntity getPlays(@RequestBody Map request) { } @PostMapping("/create") + @Operation(description = "영상 생성") public ResponseEntity createPlay(@RequestBody Map request) { RequestUtil.checkNeedValue( request.get("loginId"), @@ -57,11 +62,10 @@ public ResponseEntity createPlay(@RequestBody Map request) { request.get("title"), request.get("channelAvatar")); - Member member = memberService.findMemberByLoginId(request.get("loginId")); - Playlist playlist = playlistService.checkExistPlaylist(Long.parseLong(request.get("playlistId"))); - playlistService.checkOwn(playlist, member); + Playlist playlist = playlistService.getPlaylistById(Long.parseLong(request.get("playlistId"))); Play play = playService.addPlayToPlaylist( playlist, + request.get("loginId"), request.get("videoId"), Long.parseLong(request.get("start")), Long.parseLong(request.get("end")), @@ -74,5 +78,55 @@ public ResponseEntity createPlay(@RequestBody Map request) { return ResponseEntity.ok(responseBody); } + @PutMapping("/edit/time") + @Operation(description = "영상 재생시간 변경") + public ResponseEntity editPlayTime(@RequestBody Map request) { + RequestUtil.checkNeedValue( + request.get("loginId"), + request.get("id"), + request.get("start"), + request.get("end")); + + Long lId = Long.parseLong(request.get("id")); + Play play = playService.getPlayById(lId); + playService.editTime( + play, + request.get("loginId"), + Long.parseLong(request.get("start")), + Long.parseLong(request.get("end"))); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getEditResponse(request.get("id"))); + + return ResponseEntity.ok(responseBody); + } + + @PutMapping("/edit/seq") + @Operation(description = "영상 재생순서 변경") + public ResponseEntity editPlaySequence(@RequestBody PlayEditSeqRequestDto request) { + RequestUtil.checkNeedValue( + request.getLoginId(), + request.getPlaylistId(), + request.getSeqList()); + + playService.editSeq(request.getLoginId(), request.getPlaylistId(), request.getSeqList()); +// List result = request.getSeqList().stream().map(p -> ResponseUtil.getEditResponse(p.getId())).collect(Collectors.toList()); + List> result = ResponseUtil.getEditPlaysResponse(request.getSeqList()); + ResponseDto responseBody = ResponseUtil.getSuccessResponse(result); + + return ResponseEntity.ok(responseBody); + } + @DeleteMapping("/delete") + @Operation(description = "영상 삭제") + public ResponseEntity deletePlay(@RequestBody Map request) { + RequestUtil.checkNeedValue(request.get("loginId"), request.get("id")); + + Long lId = Long.parseLong(request.get("id")); + Play play = playService.getPlayById(lId); + playService.deletePlayById(play, request.get("loginId")); + + ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getDeleteResponse(request.get("id"))); + + return ResponseEntity.ok(responseBody); + } } diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 9ce640d..948775f 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -65,9 +65,7 @@ public ResponseEntity editPlaylist(@RequestBody Map request) request.get("loginId"), request.get("id"), request.get("title")); - Member member = memberService.findMemberByLoginId(request.get("loginId")); - playlistService.editPlaylistTitle(request.get("id"), request.get("title"), member); ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getEditResponse(request.get("id"))); @@ -80,8 +78,7 @@ public ResponseEntity deletePlaylist(@RequestBody Map request request.get("loginId"), request.get("id")); - Member member = memberService.findMemberByLoginId(request.get("loginId")); - playlistService.deletePlaylistById(request.get("id"), member); + playlistService.deletePlaylistById(request.get("id"), request.get("loginId")); ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getDeleteResponse(request.get("id"))); diff --git a/src/main/java/com/example/youtubedb/domain/BaseEntity.java b/src/main/java/com/example/youtubedb/domain/BaseEntity.java index 75d47f4..cf07363 100644 --- a/src/main/java/com/example/youtubedb/domain/BaseEntity.java +++ b/src/main/java/com/example/youtubedb/domain/BaseEntity.java @@ -1,6 +1,7 @@ package com.example.youtubedb.domain; import lombok.Getter; +import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index 245fad7..f3f5f63 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -4,9 +4,11 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; -import javax.persistence.*; -import java.time.LocalDate; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; @Entity @Getter @@ -17,6 +19,7 @@ public class Play extends BaseEntity { private long end; private String thumbnail; private String title; + @Setter private int sequence; private String channelAvatar; @ManyToOne @@ -48,4 +51,9 @@ public void setPlaylist(Playlist playlist) { this.playlist = playlist; playlist.getPlays().add(this); } + + public void setTime(long start, long end) { + this.start = start; + this.end = end; + } } diff --git a/src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java new file mode 100644 index 0000000..280acfa --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java @@ -0,0 +1,23 @@ +package com.example.youtubedb.dto.play; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@Getter +@NoArgsConstructor +public class PlayEditSeqRequestDto { + private String loginId; + private Long playlistId; + private List seqList; + + @Builder + public PlayEditSeqRequestDto(String loginId, Long playlistId, List seqList) { + this.loginId = loginId; + this.playlistId = playlistId; + this.seqList = seqList; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java b/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java new file mode 100644 index 0000000..a64a0b1 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java @@ -0,0 +1,34 @@ +package com.example.youtubedb.dto.play; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.Objects; + +@Getter +@NoArgsConstructor +public class PlaySeqDto { + private Long id; + private int sequence; + + @Builder + public PlaySeqDto(Long id, int sequence) { + this.id = id; + this.sequence = sequence; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlaySeqDto that = (PlaySeqDto) o; + return sequence == that.sequence; + } + + @Override + public int hashCode() { + return Objects.hash(sequence); + } +} diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index aad8ffa..bab40f8 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -15,7 +15,10 @@ public class ControllerExceptionHandler { DuplicateMemberException.class, NotExistMemberException.class, NotExistPlaylistException.class, - StartAndEndTimeException.class + StartAndEndTimeException.class, + NotExistPlayException.class, + DuplicateSeqException.class, + InvalidSeqException.class }) public ResponseEntity badRequest(Exception e) { ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()); diff --git a/src/main/java/com/example/youtubedb/exception/DuplicateSeqException.java b/src/main/java/com/example/youtubedb/exception/DuplicateSeqException.java new file mode 100644 index 0000000..bfe6f60 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/DuplicateSeqException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class DuplicateSeqException extends RuntimeException { + private final static String MESSAGE = "순서가 중복됩니다."; + + public DuplicateSeqException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/exception/InvalidSeqException.java b/src/main/java/com/example/youtubedb/exception/InvalidSeqException.java new file mode 100644 index 0000000..2a8f345 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/InvalidSeqException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class InvalidSeqException extends RuntimeException { + private final static String MESSAGE = "순서값이 올바르지 않습니다."; + + public InvalidSeqException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/exception/NotExistPlayException.java b/src/main/java/com/example/youtubedb/exception/NotExistPlayException.java new file mode 100644 index 0000000..b16a91e --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/NotExistPlayException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class NotExistPlayException extends RuntimeException { + private final static String MESSAGE = "해당 영상이 존재하지 않습니다."; + + public NotExistPlayException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/repository/PlayRepository.java b/src/main/java/com/example/youtubedb/repository/PlayRepository.java index 2453526..db9c9d2 100644 --- a/src/main/java/com/example/youtubedb/repository/PlayRepository.java +++ b/src/main/java/com/example/youtubedb/repository/PlayRepository.java @@ -2,7 +2,10 @@ import com.example.youtubedb.domain.Play; +import java.util.Optional; + public interface PlayRepository { Play save(Play play); + Optional findById(Long id); void deleteById(Long id); } diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index cf70da9..8d91755 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -3,13 +3,16 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; -import com.example.youtubedb.exception.InvalidAccessException; -import com.example.youtubedb.exception.StartAndEndTimeException; +import com.example.youtubedb.dto.play.PlaySeqDto; +import com.example.youtubedb.exception.*; import com.example.youtubedb.repository.PlayRepository; +import com.example.youtubedb.util.RequestUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; +import java.util.Collections; import java.util.List; @Service @@ -24,6 +27,7 @@ public PlayService(PlayRepository playRepository) { public Play addPlayToPlaylist( Playlist playlist, + String loginId, String videoId, Long start, Long end, @@ -31,6 +35,7 @@ public Play addPlayToPlaylist( String title, String channelAvatar) { checkTime(start, end); + RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); int sequence = playlist.getPlays().size() + 1; @@ -44,6 +49,7 @@ public Play addPlayToPlaylist( .channelAvatar(channelAvatar) .build(); play.setPlaylist(playlist); + playRepository.save(play); return play; } @@ -54,15 +60,61 @@ private void checkTime(Long start, Long end) { } } - public List getPlaysInPlaylist(Playlist playlist, Member member) { - validateWatch(playlist, member); + public List getPlaysInPlaylist(Playlist playlist, String loginId) { + validateWatch(playlist, loginId); return playlist.getPlays(); } - private void validateWatch(Playlist playlist, Member member) { - if (!playlist.getMember().getId().equals(member.getId()) && !playlist.isPublic()) { + private void validateWatch(Playlist playlist, String loginId) { + if (!playlist.getMember().getLoginId().equals(loginId) && !playlist.isPublic()) { throw new InvalidAccessException(); } } + + public void deletePlayById(Play play, String loginId) { + RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); + playRepository.deleteById(play.getId()); + } + + public Play getPlayById(Long id) { + return playRepository.findById(id).orElseThrow(NotExistPlayException::new); + } + + public void editTime(Play play, String loginId, long start, long end) { + RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); + checkTime(start, end); + play.setTime(start, end); + playRepository.save(play); + } + + public void editSeq(String loginId, Long playlistId, List seqList) { + checkSeqList(seqList); + seqList.forEach(p -> { + Play play = getPlayById(p.getId()); + RequestUtil.checkOwn(playlistId, play.getPlaylist().getId()); + RequestUtil.checkOwn(loginId, play.getPlaylist().getMember().getLoginId()); + play.setSequence(p.getSequence()); + }); + } + + private void checkSeqList(List seqList) { + int size = seqList.size(); + for (int i = 0; i < size; i++) { + checkDuplicateSeq(seqList, seqList.get(i)); + checkSeqNum(seqList.get(i).getSequence(), size); + } + } + + private void checkDuplicateSeq(List seqList, PlaySeqDto playSeqDto) { + if (Collections.frequency(seqList, playSeqDto) > 1) { + throw new DuplicateSeqException(); + } + } + + private void checkSeqNum(int sequence, int size) { + if((sequence < 1) || (sequence > size)) { + throw new InvalidSeqException(); + } + } } diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index f0d08c0..82e3155 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -12,10 +12,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import java.util.Optional; @Service @Transactional @@ -47,29 +44,25 @@ private void checkCategory(String category) { } } - public Playlist editPlaylistTitle(String id, String title, Member member) { + public Playlist editPlaylistTitle(String id, String title, String loginId) { Long lId = Long.parseLong(id); - Playlist playlist = checkExistPlaylist(lId); - checkOwn(playlist, member); + Playlist playlist = getPlaylistById(lId); + RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); + playlist.setTitle(title); return playlist; } - public void deletePlaylistById(String id, Member member) { + public void deletePlaylistById(String id, String loginId) { Long lId = Long.parseLong(id); - Playlist playlist = checkExistPlaylist(lId); - checkOwn(playlist, member); + Playlist playlist = getPlaylistById(lId); + RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); + playlistRepository.deleteById(lId); } - public Playlist checkExistPlaylist(Long lId) { + public Playlist getPlaylistById(Long lId) { return playlistRepository.findById(lId).orElseThrow(NotExistPlaylistException::new); } - - public void checkOwn(Playlist playlist, Member member) { - if (playlist.getMember() != member) { - throw new InvalidAccessException(); - } - } } diff --git a/src/main/java/com/example/youtubedb/util/RequestUtil.java b/src/main/java/com/example/youtubedb/util/RequestUtil.java index 9f89a81..78c3d04 100644 --- a/src/main/java/com/example/youtubedb/util/RequestUtil.java +++ b/src/main/java/com/example/youtubedb/util/RequestUtil.java @@ -1,5 +1,6 @@ package com.example.youtubedb.util; +import com.example.youtubedb.exception.InvalidAccessException; import com.example.youtubedb.exception.NotExistRequestValueException; import java.util.List; @@ -10,4 +11,10 @@ public static void checkNeedValue(Object ...args) { if(arg == null) throw new NotExistRequestValueException(); } } + + public static void checkOwn(Object target, Object origin) { + if (!target.equals(origin)) { + throw new InvalidAccessException(); + } + } } diff --git a/src/main/java/com/example/youtubedb/util/ResponseUtil.java b/src/main/java/com/example/youtubedb/util/ResponseUtil.java index 47a6453..e4f17d7 100644 --- a/src/main/java/com/example/youtubedb/util/ResponseUtil.java +++ b/src/main/java/com/example/youtubedb/util/ResponseUtil.java @@ -1,11 +1,15 @@ package com.example.youtubedb.util; +import com.example.youtubedb.domain.Play; import com.example.youtubedb.dto.ErrorDto; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.play.PlaySeqDto; import com.example.youtubedb.exception.NotExistRequestValueException; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class ResponseUtil { public static ResponseDto getSuccessResponse(Object data) { @@ -33,17 +37,21 @@ private static ErrorDto getErrorDto(String message, int status) { .build(); } - public static Map getDeleteResponse(String id) { + public static Map getDeleteResponse(Object id) { Map result = new HashMap<>(); result.put("id", id); result.put("deleted", true); return result; } - public static Map getEditResponse(String id) { + public static Map getEditResponse(Object id) { Map result = new HashMap<>(); result.put("id", id); result.put("edited", true); return result; } + + public static List> getEditPlaysResponse(List list) { + return list.stream().map(m -> getEditResponse(m.getId())).collect(Collectors.toList()); + } } diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index 2118672..aa5e4a4 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -3,14 +3,15 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; -import com.example.youtubedb.exception.InvalidAccessException; -import com.example.youtubedb.exception.StartAndEndTimeException; +import com.example.youtubedb.dto.play.PlaySeqDto; +import com.example.youtubedb.exception.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -53,6 +54,7 @@ void setup() { // when Play play = playService.addPlayToPlaylist( playlist, + member.getLoginId(), videoId, start, end, @@ -83,6 +85,7 @@ void setup() { Exception e = assertThrows(StartAndEndTimeException.class , () -> playService.addPlayToPlaylist( playlist, + member.getLoginId(), videoId, 0L, -5L, @@ -102,6 +105,7 @@ void setup() { Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); playService.addPlayToPlaylist( playlist, + member.getLoginId(), videoId, start, end, @@ -110,7 +114,7 @@ void setup() { channelAvatar); // when - List plays = playService.getPlaysInPlaylist(playlist, member); + List plays = playService.getPlaysInPlaylist(playlist, member.getLoginId()); // then assertAll( @@ -127,6 +131,7 @@ void setup() { Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); playService.addPlayToPlaylist( playlist, + member.getLoginId(), videoId, start, end, @@ -135,9 +140,194 @@ void setup() { channelAvatar); // when - Exception e = assertThrows(InvalidAccessException.class, () -> playService.getPlaysInPlaylist(playlist, other)); + Exception e = assertThrows(InvalidAccessException.class, () -> playService.getPlaysInPlaylist(playlist, other.getLoginId())); // then assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); } + + @Test + void 영상_시간_수정() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Play play = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + // when + playService.editTime(play, member.getLoginId(), 50L, 100L); + Play editedPlay = playService.getPlayById(play.getId()); + + // then + assertAll( + () -> assertThat(editedPlay.getStart()).isEqualTo(50L), + () -> assertThat(editedPlay.getEnd()).isEqualTo(100L) + ); + } + + @Test + void 영상_순서_수정() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Play play1 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play1", + channelAvatar); + Play play2 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play2", + channelAvatar); + Play play3 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play3", + channelAvatar); + List seqList = new ArrayList<>(); + seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(3).build()); + seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(1).build()); + seqList.add(PlaySeqDto.builder().id(play3.getId()).sequence(2).build()); + + // when + playService.editSeq(member.getLoginId(), playlist.getId(), seqList); + + // then + assertAll( + () -> assertThat(play1.getSequence()).isEqualTo(3), + () -> assertThat(play2.getSequence()).isEqualTo(1), + () -> assertThat(play3.getSequence()).isEqualTo(2) + ); + } + + @Test + void 영상_순서_수정_순서이상() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Play play1 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play1", + channelAvatar); + Play play2 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play2", + channelAvatar); + Play play3 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play3", + channelAvatar); + List seqList = new ArrayList<>(); + seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(3).build()); + seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(4).build()); + seqList.add(PlaySeqDto.builder().id(play3.getId()).sequence(2).build()); + + // when + Exception e = assertThrows(InvalidSeqException.class, () -> playService.editSeq(member.getLoginId(), playlist.getId(), seqList)); + + // then + assertThat(e.getMessage()).isEqualTo(InvalidSeqException.getErrorMessage()); + } + + @Test + void 영상_순서_수정_중복() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Play play1 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play1", + channelAvatar); + Play play2 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play2", + channelAvatar); + Play play3 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + "play3", + channelAvatar); + List seqList = new ArrayList<>(); + seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(1).build()); + seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(2).build()); + seqList.add(PlaySeqDto.builder().id(play3.getId()).sequence(2).build()); + + // when + Exception e = assertThrows(DuplicateSeqException.class, () -> playService.editSeq(member.getLoginId(), playlist.getId(), seqList)); + + // then + assertThat(e.getMessage()).isEqualTo(DuplicateSeqException.getErrorMessage()); + } + + @Test + void 영상_삭제() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Play play = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + // when + playService.deletePlayById(play, "device001"); + Exception e = assertThrows(NotExistPlayException.class, () -> playService.getPlayById(play.getId())); + + // then + assertThat(e.getMessage()).isEqualTo(NotExistPlayException.getErrorMessage()); + } } diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index 8bb6dca..ce33eda 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -41,7 +41,7 @@ class PlaylistServiceIntegrationTest { () -> assertThat(playlist.getTitle()).isEqualTo(title), () -> assertThat(playlist.getCategory()).isEqualTo(Category.GAME), () -> assertThat(playlist.isPublic()).isEqualTo(true), - () -> assertThat(playlist.getMember()).isEqualTo(member), + () -> assertThat(playlist.getMember().getId()).isEqualTo(member.getId()), () -> assertThat(playlist.getLikeCnt()).isEqualTo(0) ); } @@ -65,14 +65,22 @@ class PlaylistServiceIntegrationTest { void 플레이리스트_조회() { // given Member member = memberService.registerNon("devide001"); - playlistService.createPlaylist("myList", "false", "GAME", member); - playlistService.createPlaylist("myList2", "false", "OTHER", member); + String title = "myList"; + String isPublic = "false"; + String category = "GAME"; + Playlist playlist = playlistService.createPlaylist(title, isPublic, category, member); // when - List playlists = member.getPlaylists(); + Playlist result = playlistService.getPlaylistById(playlist.getId()); // then - assertThat(playlists.size()).isEqualTo(2); + assertAll( + () -> assertThat(result.getId()).isEqualTo(playlist.getId()), + () -> assertThat(result.getTitle()).isEqualTo(title), + () -> assertThat(result.isPublic()).isEqualTo(Boolean.parseBoolean(isPublic)), + () -> assertThat(result.getCategory()).isEqualTo(Category.valueOf(category)), + () -> assertThat(result.getMember().getId()).isEqualTo(member.getId()) + ); } @Test @@ -82,7 +90,7 @@ class PlaylistServiceIntegrationTest { Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); String newTitle = "newList"; // when - Playlist newList = playlistService.editPlaylistTitle(playlist.getId().toString(), newTitle, member); + Playlist newList = playlistService.editPlaylistTitle(playlist.getId().toString(), newTitle, member.getLoginId()); // then assertThat(newList.getTitle()).isEqualTo(newTitle); @@ -94,7 +102,7 @@ class PlaylistServiceIntegrationTest { Member member = memberService.registerNon("devide001"); Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); // when - Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.checkExistPlaylist(100L)); + Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.getPlaylistById(100L)); // then assertThat(e.getMessage()).isEqualTo(NotExistPlaylistException.getErrorMessage()); @@ -107,26 +115,12 @@ class PlaylistServiceIntegrationTest { Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); // when - playlistService.deletePlaylistById(playlist.getId().toString(), member); + playlistService.deletePlaylistById(playlist.getId().toString(), member.getLoginId()); Exception e = assertThrows(NotExistPlaylistException.class, - () -> playlistService.checkExistPlaylist(playlist.getId()) + () -> playlistService.getPlaylistById(playlist.getId()) ); // then assertThat(e.getMessage()).isEqualTo(NotExistPlaylistException.getErrorMessage()); } - - @Test - void 타유저_접근() { - // given - Member member1 = memberService.registerNon("device001"); - Member member2 = memberService.registerNon("device002"); - Playlist playlist = playlistService.createPlaylist("title", "false", "OTHER", member1); - - // when - Exception e = assertThrows(InvalidAccessException.class, () -> playlistService.checkOwn(playlist, member2)); - - // then - assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); - } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java index 4e440ac..460a23c 100644 --- a/src/test/java/com/example/youtubedb/util/RequestUtilTest.java +++ b/src/test/java/com/example/youtubedb/util/RequestUtilTest.java @@ -1,5 +1,6 @@ package com.example.youtubedb.util; +import com.example.youtubedb.exception.InvalidAccessException; import com.example.youtubedb.exception.NotExistRequestValueException; import org.junit.jupiter.api.Test; @@ -21,4 +22,17 @@ class RequestUtilTest { // then assertThat(e.getMessage()).isEqualTo(NotExistRequestValueException.getErrorMessage()); } + + @Test + void 요청_본인X() { + // given + String loginId = "user"; + String other = "other"; + + // when + Exception e = assertThrows(InvalidAccessException.class, () -> RequestUtil.checkOwn(other, loginId)); + + // then + assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); + } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java b/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java index f320c06..4c2e5c8 100644 --- a/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java +++ b/src/test/java/com/example/youtubedb/util/ResponseUtilTest.java @@ -1,9 +1,13 @@ package com.example.youtubedb.util; +import com.example.youtubedb.domain.Play; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.play.PlaySeqDto; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -62,7 +66,7 @@ class ResponseUtilTest { @Test void 삭제시_응답객체() { // given - String id = "1"; + Long id = 1L; // then Map result = ResponseUtil.getDeleteResponse(id); @@ -73,4 +77,24 @@ class ResponseUtilTest { () -> assertThat(result.get("deleted")).isEqualTo(true) ); } + + @Test + void 영상_순서목록_수정시_응답객체() { + // given + List seqList = new ArrayList<>(); + seqList.add(PlaySeqDto.builder().id(1L).sequence(1).build()); + seqList.add(PlaySeqDto.builder().id(2L).sequence(2).build()); + seqList.add(PlaySeqDto.builder().id(3L).sequence(2).build()); + + // then + List> responseBody = ResponseUtil.getEditPlaysResponse(seqList); + + // when + assertAll( + () -> assertThat(responseBody.size()).isEqualTo(3), + () -> assertThat(responseBody.get(0).get("id")).isEqualTo(1L), + () -> assertThat(responseBody.get(1).get("id")).isEqualTo(2L), + () -> assertThat(responseBody.get(2).get("id")).isEqualTo(3L) + ); + } } \ No newline at end of file From 73aaeb026f6e7cb8fbf81411d40e1f1412d5e558 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Fri, 30 Jul 2021 23:35:52 +0900 Subject: [PATCH 08/46] =?UTF-8?q?[ADD]=20=EC=8A=A4=EC=9B=A8=EA=B1=B03=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberController.java | 42 +++- .../youtubedb/controller/PlayController.java | 200 ++++++++++++------ .../controller/PlaylistController.java | 133 +++++++++--- .../youtubedb/dto/BaseResponseFailDto.java | 11 + .../youtubedb/dto/BaseResponseSuccessDto.java | 11 + .../dto/error/BadRequestFailResponseDto.java | 22 ++ .../error/NotAcceptableFailResponseDto.java | 22 ++ .../dto/error/ServerErrorFailResponseDto.java | 22 ++ .../request/NonMemberCreateRequestDto.java | 18 ++ .../response/NonMemberCreateResponseDto.java | 16 ++ .../youtubedb/dto/play/PlaySeqDto.java | 3 + .../play/request/PlayCreateRequestDto.java | 43 ++++ .../{ => request}/PlayEditSeqRequestDto.java | 10 +- .../play/request/PlayEditTimeRequestDto.java | 24 +++ .../play/response/PlayCreateResponseDto.java | 16 ++ .../play/response/PlayDeleteResponseDto.java | 17 ++ .../play/response/PlayEditSeqResponseDto.java | 18 ++ .../response/PlayEditTimeResponseDto.java | 17 ++ .../play/response/PlaysGetResponseDto.java | 18 ++ .../request/PlaylistCreateRequestDto.java | 28 +++ .../request/PlaylistEditTitleRequestDto.java | 21 ++ .../response/PlaylistCreateResponseDto.java | 16 ++ .../response/PlaylistDeleteResponseDto.java | 17 ++ .../PlaylistEditTitleResponseDto.java | 17 ++ .../response/PlaylistGetResponseDto.java | 18 ++ .../exception/ControllerExceptionHandler.java | 20 +- .../youtubedb/service/PlayService.java | 10 +- .../youtubedb/service/PlaylistService.java | 22 +- .../example/youtubedb/util/RequestUtil.java | 10 +- src/main/resources/application.yaml | 2 +- src/main/resources/swagger.yaml | 29 +++ .../service/PlayServiceIntegrationTest.java | 60 +++--- .../PlaylistServiceIntegrationTest.java | 21 +- 33 files changed, 776 insertions(+), 178 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/dto/BaseResponseFailDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/BaseResponseSuccessDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java rename src/main/java/com/example/youtubedb/dto/play/{ => request}/PlayEditSeqRequestDto.java (50%) create mode 100644 src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/response/PlayCreateResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/response/PlayDeleteResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/response/PlayEditSeqResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/response/PlayEditTimeResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/play/response/PlaysGetResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistCreateResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistDeleteResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistEditTitleResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistGetResponseDto.java create mode 100644 src/main/resources/swagger.yaml diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index b92220b..166efe5 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -1,11 +1,20 @@ package com.example.youtubedb.controller; import com.example.youtubedb.domain.Member; -import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.error.BadRequestFailResponseDto; +import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.member.request.NonMemberCreateRequestDto; +import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; -import com.example.youtubedb.util.ResponseUtil; +import io.swagger.v3.oas.annotations.Operation; +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.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -13,8 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; - +@Tag(name = "회원 관련 API") @RestController @RequestMapping("/api/member") public class MemberController { @@ -28,15 +36,27 @@ public MemberController(MemberService memberService, PlaylistService playlistSer this.playlistService = playlistService; } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "비회원 생성 성공", + content = @Content(schema = @Schema(implementation = NonMemberCreateResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 중복된 아이디 존재\n" + + "2. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "가입", description = "비회원 가입") @PostMapping("/register/non") - public ResponseEntity registerNonMember(@RequestBody Map request) { - RequestUtil.checkNeedValue(request.get("deviceId")); - - String deviceId = request.get("deviceId"); - Member nonMember = memberService.registerNon(deviceId); - playlistService.createPlaylist("default", "false", "OTHER", nonMember); + public ResponseEntity registerNonMember(@RequestBody NonMemberCreateRequestDto request) { + RequestUtil.checkNeedValue(request.getDeviceId()); + Member nonMember = memberService.registerNon(request.getDeviceId()); + playlistService.createPlaylist("default", false, "OTHER", nonMember); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(nonMember); + BaseResponseSuccessDto responseBody = new NonMemberCreateResponseDto(nonMember); return ResponseEntity.ok(responseBody); } diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index a7cf8c2..4af3ee4 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -2,14 +2,24 @@ import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.error.BadRequestFailResponseDto; +import com.example.youtubedb.dto.error.NotAcceptableFailResponseDto; +import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.play.request.*; +import com.example.youtubedb.dto.play.response.*; import com.example.youtubedb.dto.ResponseDto; -import com.example.youtubedb.dto.play.PlayEditSeqRequestDto; -import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.dto.playlist.response.PlaylistDeleteResponseDto; import com.example.youtubedb.service.PlayService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import com.example.youtubedb.util.ResponseUtil; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -21,111 +31,177 @@ // play, playlist의 변경 작업 시 본인인지 확인하는 부분을 aop로 빼도 괜찮을 듯? // 근데 세부사항이 좀 다를 수 있어서..우선 Util로 빼놓자! -@RestController @Tag(name = "영상 관련 API") +@RestController @RequestMapping("/api/play") public class PlayController { private final PlayService playService; private final PlaylistService playlistService; - private final MemberService memberService; @Autowired - public PlayController(PlayService playService, PlaylistService playlistService, MemberService memberService) { + public PlayController(PlayService playService, PlaylistService playlistService) { this.playService = playService; this.playlistService = playlistService; - this.memberService = memberService; } - @GetMapping("/list") - @Operation(description = "영상 목록 조회") - public ResponseEntity getPlays(@RequestBody Map request) { - RequestUtil.checkNeedValue(request.get("loginId"), request.get("playlistId")); - - Playlist playlist = playlistService.getPlaylistById(Long.parseLong(request.get("playlistId"))); - List plays = playService.getPlaysInPlaylist(playlist, request.get("loginId")); - - ResponseDto responseBody = ResponseUtil.getSuccessResponse(plays); + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = PlaysGetResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 플레이 리스트 존재 X" , + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @GetMapping("/list/{playlistId}") + @Operation(summary = "조회", description = "영상 목록 조회") + public ResponseEntity getPlays(@Parameter @PathVariable("playlistId") Long playlistId) { + RequestUtil.checkNeedValue(playlistId); + + Playlist playlist = playlistService.getPlaylistById(playlistId); + List plays = playService.getPlaysInPlaylist(playlist, "loginId?"); + + BaseResponseSuccessDto responseBody = new PlaysGetResponseDto(plays); return ResponseEntity.ok(responseBody); } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "생성 성공", + content = @Content(schema = @Schema(implementation = PlayCreateResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 플레이 리스트 존재 X\n" + + "3. 시간값 바르지 않음", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) @PostMapping("/create") - @Operation(description = "영상 생성") - public ResponseEntity createPlay(@RequestBody Map request) { + @Operation(summary = "생성", description = "영상 생성") + public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto request) { RequestUtil.checkNeedValue( - request.get("loginId"), - request.get("playlistId"), - request.get("videoId"), - request.get("start"), - request.get("end"), - request.get("thumbnail"), - request.get("title"), - request.get("channelAvatar")); - - Playlist playlist = playlistService.getPlaylistById(Long.parseLong(request.get("playlistId"))); +// request.getLoginId(), + request.getPlaylistId(), + request.getVideoId(), + request.getStart(), + request.getEnd(), + request.getThumbnail(), + request.getTitle(), + request.getChannelAvatar()); + + Playlist playlist = playlistService.getPlaylistById(request.getPlaylistId()); Play play = playService.addPlayToPlaylist( playlist, - request.get("loginId"), - request.get("videoId"), - Long.parseLong(request.get("start")), - Long.parseLong(request.get("end")), - request.get("thumbnail"), - request.get("title"), - request.get("channelAvatar")); + "loginId?", + request.getVideoId(), + request.getStart(), + request.getEnd(), + request.getThumbnail(), + request.getTitle(), + request.getChannelAvatar()); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(play); + BaseResponseSuccessDto responseBody = new PlayCreateResponseDto(play); return ResponseEntity.ok(responseBody); } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "수정 성공", + content = @Content(schema = @Schema(implementation = PlayEditTimeResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 영상 존재 X\n" + + "3. 시간값 바르지 않음", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) @PutMapping("/edit/time") - @Operation(description = "영상 재생시간 변경") - public ResponseEntity editPlayTime(@RequestBody Map request) { + @Operation(summary = "재생 시간 변경", description = "영상 재생시간 변경") + public ResponseEntity editPlayTime(@RequestBody PlayEditTimeRequestDto request) { RequestUtil.checkNeedValue( - request.get("loginId"), - request.get("id"), - request.get("start"), - request.get("end")); + request.getId(), + request.getStart(), + request.getEnd()); - Long lId = Long.parseLong(request.get("id")); - Play play = playService.getPlayById(lId); + Play play = playService.getPlayById(request.getId()); playService.editTime( play, - request.get("loginId"), - Long.parseLong(request.get("start")), - Long.parseLong(request.get("end"))); + "loginId?", + request.getStart(), + play.getEnd()); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getEditResponse(request.get("id"))); + BaseResponseSuccessDto responseBody = new PlayEditTimeResponseDto(request.getId()); return ResponseEntity.ok(responseBody); } @PutMapping("/edit/seq") - @Operation(description = "영상 재생순서 변경") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "수정 성공", + content = @Content(schema = @Schema(implementation = PlayEditSeqResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 플레이 리스트 존재 X\n" + + "3. 순서가 바르지 않음(중복, 유효X)\n" + + "4. 영상 존재 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "406", + description = "올바르지 못한 접근", + content = @Content(schema = @Schema(implementation = NotAcceptableFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "순서 변경", description = "영상 재생순서 변경") public ResponseEntity editPlaySequence(@RequestBody PlayEditSeqRequestDto request) { RequestUtil.checkNeedValue( - request.getLoginId(), request.getPlaylistId(), request.getSeqList()); - playService.editSeq(request.getLoginId(), request.getPlaylistId(), request.getSeqList()); -// List result = request.getSeqList().stream().map(p -> ResponseUtil.getEditResponse(p.getId())).collect(Collectors.toList()); + playService.editSeq("loginId?", request.getPlaylistId(), request.getSeqList()); List> result = ResponseUtil.getEditPlaysResponse(request.getSeqList()); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(result); + + BaseResponseSuccessDto responseBody = new PlayEditSeqResponseDto(request.getSeqList()); return ResponseEntity.ok(responseBody); } - @DeleteMapping("/delete") - @Operation(description = "영상 삭제") - public ResponseEntity deletePlay(@RequestBody Map request) { - RequestUtil.checkNeedValue(request.get("loginId"), request.get("id")); - - Long lId = Long.parseLong(request.get("id")); - Play play = playService.getPlayById(lId); - playService.deletePlayById(play, request.get("loginId")); - - ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getDeleteResponse(request.get("id"))); + @DeleteMapping("/delete/{id}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "삭제 성공", + content = @Content(schema = @Schema(implementation = PlayDeleteResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 영상 존재 X\n", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "삭제", description = "영상 삭제") + public ResponseEntity deletePlay(@Parameter @PathVariable("id") Long id) { + RequestUtil.checkNeedValue(id); + + Play play = playService.getPlayById(id); + playService.deletePlayById(play, "loginId?"); + + BaseResponseSuccessDto responseBody = new PlayDeleteResponseDto(id); return ResponseEntity.ok(responseBody); } diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 948775f..e6d7011 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -2,21 +2,37 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.BaseResponseSuccessDto; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.error.BadRequestFailResponseDto; +import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; +import com.example.youtubedb.dto.play.request.PlayCreateRequestDto; +import com.example.youtubedb.dto.playlist.request.PlaylistCreateRequestDto; +import com.example.youtubedb.dto.playlist.request.PlaylistEditTitleRequestDto; +import com.example.youtubedb.dto.playlist.response.PlaylistCreateResponseDto; +import com.example.youtubedb.dto.playlist.response.PlaylistDeleteResponseDto; +import com.example.youtubedb.dto.playlist.response.PlaylistEditTitleResponseDto; +import com.example.youtubedb.dto.playlist.response.PlaylistGetResponseDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import com.example.youtubedb.util.ResponseUtil; -import com.fasterxml.jackson.databind.util.JSONWrappedObject; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; import java.util.List; -import java.util.Map; // Spring Security로 JWT 적용시 변경 필요 +@Tag(name = "플레이 리스트 관련 API") @RestController @RequestMapping("/api/playlist") public class PlaylistController { @@ -29,60 +45,111 @@ public PlaylistController(PlaylistService playlistService, MemberService memberS this.memberService = memberService; } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = PlaylistGetResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 멤버 존재 X" , + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "조회", description = "플레이 리스트들 조회") @GetMapping("/{loginId}") - public ResponseEntity getPlaylist(@PathVariable("loginId") String loginId) { + public ResponseEntity getPlaylist(@Parameter @PathVariable("loginId") String loginId) { Member member = memberService.findMemberByLoginId(loginId); List playlists = member.getPlaylists(); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(playlists); + BaseResponseSuccessDto responseBody = new PlaylistGetResponseDto(playlists); return ResponseEntity.ok(responseBody); } - + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "생성 성공", + content = @Content(schema = @Schema(implementation = PlaylistCreateResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 멤버 존재 X\n" + + "3. 카테고리 존재 X" , + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) @PostMapping("/create") - public ResponseEntity createPlaylist(@RequestBody Map request) { + @Operation(summary = "생성", description = "플레이 리스트 생성") + public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto request) { // 여긴 임시로 loginId 필요 RequestUtil.checkNeedValue( - request.get("loginId"), - request.get("title"), - request.get("public"), - request.get("category")); - - Member member = memberService.findMemberByLoginId(request.get("loginId")); + request.getLoginId(), + request.getTitle(), + request.getIsPublic(), + request.getCategory()); + Member member = memberService.findMemberByLoginId(request.getLoginId()); Playlist playlist = playlistService.createPlaylist( - request.get("title"), - request.get("public"), - request.get("category"), + request.getTitle(), + request.getIsPublic(), + request.getCategory(), member); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(playlist); + BaseResponseSuccessDto responseBody = new PlaylistCreateResponseDto(playlist); return ResponseEntity.ok(responseBody); } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "수정 성공", + content = @Content(schema = @Schema(implementation = PlaylistEditTitleResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 멤버 존재 X\n" + + "3. 플레이 리스트 존재 X" , + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) @PutMapping("/edit") - public ResponseEntity editPlaylist(@RequestBody Map request) { + @Operation(summary = "제목 수정", description = "플레이 리스트 제목 수정") + public ResponseEntity editPlaylist(@RequestBody PlaylistEditTitleRequestDto request) { RequestUtil.checkNeedValue( - request.get("loginId"), - request.get("id"), - request.get("title")); - Member member = memberService.findMemberByLoginId(request.get("loginId")); + request.getId(), + request.getTitle()); + playlistService.editPlaylistTitle(request.getId(), request.getTitle(), "loginId"); - ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getEditResponse(request.get("id"))); + BaseResponseSuccessDto responseBody = new PlaylistEditTitleResponseDto(request.getId()); return ResponseEntity.ok(responseBody); } - @DeleteMapping("/delete") - public ResponseEntity deletePlaylist(@RequestBody Map request) { - RequestUtil.checkNeedValue( - request.get("loginId"), - request.get("id")); - - playlistService.deletePlaylistById(request.get("id"), request.get("loginId")); - - ResponseDto responseBody = ResponseUtil.getSuccessResponse(ResponseUtil.getDeleteResponse(request.get("id"))); + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "삭제 성공", + content = @Content(schema = @Schema(implementation = PlaylistDeleteResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n " + + "1. 필요값 X\n" + + "2. 플레이 리스트 존재 X" , + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @DeleteMapping("/delete/{id}") + @Operation(summary = "삭제", description = "플레이 리스트 삭제") + public ResponseEntity deletePlaylist(@Parameter @PathVariable("id") Long id) { + RequestUtil.checkNeedValue(id); + playlistService.deletePlaylistById(id, "loginId?"); + + BaseResponseSuccessDto responseBody = new PlaylistDeleteResponseDto(id); return ResponseEntity.ok(responseBody); } - } diff --git a/src/main/java/com/example/youtubedb/dto/BaseResponseFailDto.java b/src/main/java/com/example/youtubedb/dto/BaseResponseFailDto.java new file mode 100644 index 0000000..89d0901 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/BaseResponseFailDto.java @@ -0,0 +1,11 @@ +package com.example.youtubedb.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class BaseResponseFailDto { + private final boolean success = false; + @Schema(defaultValue = "null") + private final Object response = null; +} diff --git a/src/main/java/com/example/youtubedb/dto/BaseResponseSuccessDto.java b/src/main/java/com/example/youtubedb/dto/BaseResponseSuccessDto.java new file mode 100644 index 0000000..4bc69f1 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/BaseResponseSuccessDto.java @@ -0,0 +1,11 @@ +package com.example.youtubedb.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class BaseResponseSuccessDto { + private final boolean success = true; + @Schema(defaultValue = "null") + private final ErrorDto error = null; +} diff --git a/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java new file mode 100644 index 0000000..b5cb32f --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java @@ -0,0 +1,22 @@ +package com.example.youtubedb.dto.error; + +import com.example.youtubedb.dto.BaseResponseFailDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class BadRequestFailResponseDto extends BaseResponseFailDto { + @Schema(description = "에러 상태 코드", defaultValue = "400") + private int status; + @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") + private String message; + + @Builder + public BadRequestFailResponseDto(int status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java new file mode 100644 index 0000000..798d8e9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java @@ -0,0 +1,22 @@ +package com.example.youtubedb.dto.error; + +import com.example.youtubedb.dto.BaseResponseFailDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class NotAcceptableFailResponseDto extends BaseResponseFailDto { + @Schema(description = "에러 상태 코드", defaultValue = "406") + private int status; + @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") + private String message; + + @Builder + public NotAcceptableFailResponseDto(int status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java new file mode 100644 index 0000000..d731c81 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java @@ -0,0 +1,22 @@ +package com.example.youtubedb.dto.error; + +import com.example.youtubedb.dto.BaseResponseFailDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ServerErrorFailResponseDto extends BaseResponseFailDto { + @Schema(description = "에러 상태 코드", defaultValue = "500") + private int status; + @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") + private String message; + + @Builder + public ServerErrorFailResponseDto(int status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java new file mode 100644 index 0000000..a163678 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java @@ -0,0 +1,18 @@ +package com.example.youtubedb.dto.member.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class NonMemberCreateRequestDto { + @Schema(description = "장치 ID", example = "device001") + private String deviceId; + + @Builder + public NonMemberCreateRequestDto(String deviceId) { + this.deviceId = deviceId; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java new file mode 100644 index 0000000..4126b71 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java @@ -0,0 +1,16 @@ +package com.example.youtubedb.dto.member.response; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class NonMemberCreateResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 비회원") + private final Member response; + + public NonMemberCreateResponseDto(Member response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java b/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java index a64a0b1..503129d 100644 --- a/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/PlaySeqDto.java @@ -1,5 +1,6 @@ package com.example.youtubedb.dto.play; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,7 +11,9 @@ @Getter @NoArgsConstructor public class PlaySeqDto { + @Schema(description = "영상 아이디", example = "1") private Long id; + @Schema(description = "영상 순서", example = "1") private int sequence; @Builder diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java new file mode 100644 index 0000000..96b9bb5 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java @@ -0,0 +1,43 @@ +package com.example.youtubedb.dto.play.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PlayCreateRequestDto { + @Schema(description = "플레이 리스트 아이디" , example = "1") + private Long playlistId; + @Schema(description = "비디오 아이디" , example = "video123") + private String videoId; + @Schema(description = "시작 시간" , example = "100") + private Long start; + @Schema(description = "끝 시간" , example = "1000") + private Long end; + @Schema(description = "썸네일 이미지 주소" , example = "img1") + private String thumbnail; + @Schema(description = "제목" , example = "제목1") + private String title; + @Schema(description = "아바타 이미지 주소" , example = "img2") + private String channelAvatar; + + @Builder + public PlayCreateRequestDto( + Long playlistId, + String videoId, + Long start, + Long end, + String thumbnail, + String title, + String channelAvatar) { + this.playlistId = playlistId; + this.videoId = videoId; + this.start = start; + this.end = end; + this.thumbnail = thumbnail; + this.title = title; + this.channelAvatar = channelAvatar; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java similarity index 50% rename from src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java rename to src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java index 280acfa..418dc17 100644 --- a/src/main/java/com/example/youtubedb/dto/play/PlayEditSeqRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java @@ -1,5 +1,7 @@ -package com.example.youtubedb.dto.play; +package com.example.youtubedb.dto.play.request; +import com.example.youtubedb.dto.play.PlaySeqDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,13 +12,13 @@ @Getter @NoArgsConstructor public class PlayEditSeqRequestDto { - private String loginId; + @Schema(description = "로그인 아이디", example = "tester") private Long playlistId; + @Schema(description = "영상 순서 목록") private List seqList; @Builder - public PlayEditSeqRequestDto(String loginId, Long playlistId, List seqList) { - this.loginId = loginId; + public PlayEditSeqRequestDto(Long playlistId, List seqList) { this.playlistId = playlistId; this.seqList = seqList; } diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java new file mode 100644 index 0000000..dd5ffcd --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java @@ -0,0 +1,24 @@ +package com.example.youtubedb.dto.play.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PlayEditTimeRequestDto { + @Schema(description = "플레이 아이디", example = "1") + private Long id; + @Schema(description = "시작 시간", example = "100") + private Long start; + @Schema(description = "끝 시간", example = "1000") + private Long end; + + @Builder + public PlayEditTimeRequestDto(Long id, Long start, Long end) { + this.id = id; + this.start = start; + this.end = end; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/response/PlayCreateResponseDto.java b/src/main/java/com/example/youtubedb/dto/play/response/PlayCreateResponseDto.java new file mode 100644 index 0000000..e2d18bd --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/response/PlayCreateResponseDto.java @@ -0,0 +1,16 @@ +package com.example.youtubedb.dto.play.response; + +import com.example.youtubedb.domain.Play; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlayCreateResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 영상") + private final Play response; + + public PlayCreateResponseDto(Play response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/response/PlayDeleteResponseDto.java b/src/main/java/com/example/youtubedb/dto/play/response/PlayDeleteResponseDto.java new file mode 100644 index 0000000..4db65c0 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/response/PlayDeleteResponseDto.java @@ -0,0 +1,17 @@ +package com.example.youtubedb.dto.play.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlayDeleteResponseDto extends BaseResponseSuccessDto { + @Schema(description = "삭제된 영상 아이디") + private final Long id; + @Schema(description = "삭제 여부") + private final boolean deleted = true; + + public PlayDeleteResponseDto(Long id) { + this.id = id; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/response/PlayEditSeqResponseDto.java b/src/main/java/com/example/youtubedb/dto/play/response/PlayEditSeqResponseDto.java new file mode 100644 index 0000000..23cd9d3 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/response/PlayEditSeqResponseDto.java @@ -0,0 +1,18 @@ +package com.example.youtubedb.dto.play.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.play.PlaySeqDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.util.List; +import java.util.Map; + +@Getter +public class PlayEditSeqResponseDto extends BaseResponseSuccessDto { + @Schema(description = "수정 목록") + private final List response; + public PlayEditSeqResponseDto(List response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/response/PlayEditTimeResponseDto.java b/src/main/java/com/example/youtubedb/dto/play/response/PlayEditTimeResponseDto.java new file mode 100644 index 0000000..90252b6 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/response/PlayEditTimeResponseDto.java @@ -0,0 +1,17 @@ +package com.example.youtubedb.dto.play.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlayEditTimeResponseDto extends BaseResponseSuccessDto { + @Schema(description = "수정된 영상 아이디") + private final Long id; + @Schema(description = "수정 여부") + private final boolean edited = true; + + public PlayEditTimeResponseDto(Long id) { + this.id = id; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/play/response/PlaysGetResponseDto.java b/src/main/java/com/example/youtubedb/dto/play/response/PlaysGetResponseDto.java new file mode 100644 index 0000000..84bff16 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/play/response/PlaysGetResponseDto.java @@ -0,0 +1,18 @@ +package com.example.youtubedb.dto.play.response; + +import com.example.youtubedb.domain.Play; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.util.List; + +@Getter +public class PlaysGetResponseDto extends BaseResponseSuccessDto { + @Schema(description = "리스트 내 영상 목록") + private final List response; + + public PlaysGetResponseDto(List response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java new file mode 100644 index 0000000..aefe3f9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java @@ -0,0 +1,28 @@ +package com.example.youtubedb.dto.playlist.request; + +import com.example.youtubedb.util.RequestUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PlaylistCreateRequestDto { + @Schema(description = "로그인 아이디", example = "tester") + private String loginId; + @Schema(description = "제목", example = "영상 목록 1") + private String title; + @Schema(description = "공개 여부", example = "true") + private Boolean isPublic; + @Schema(description = "카테고리", example = "GAME") + private String category; + + @Builder + public PlaylistCreateRequestDto(String loginId, String title, Boolean isPublic, String category) { + this.loginId = loginId; + this.title = title; + this.isPublic = isPublic; + this.category = category; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java new file mode 100644 index 0000000..58a8e26 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java @@ -0,0 +1,21 @@ +package com.example.youtubedb.dto.playlist.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PlaylistEditTitleRequestDto { + @Schema(description = "플레이 리스트 아이디", example = "1") + private Long id; + @Schema(description = "제목", example = "영상 목록 1") + private String title; + + @Builder + public PlaylistEditTitleRequestDto(Long id, String title) { + this.id = id; + this.title = title; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistCreateResponseDto.java b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistCreateResponseDto.java new file mode 100644 index 0000000..0fdbe9a --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistCreateResponseDto.java @@ -0,0 +1,16 @@ +package com.example.youtubedb.dto.playlist.response; + +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlaylistCreateResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 플레이리스트") + private final Playlist response; + + public PlaylistCreateResponseDto(Playlist response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistDeleteResponseDto.java b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistDeleteResponseDto.java new file mode 100644 index 0000000..91e61bb --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistDeleteResponseDto.java @@ -0,0 +1,17 @@ +package com.example.youtubedb.dto.playlist.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlaylistDeleteResponseDto extends BaseResponseSuccessDto { + @Schema(description = "삭제된 플레이리스트 아이디") + private final Long id; + @Schema(description = "삭제 여부") + private final boolean deleted = true; + + public PlaylistDeleteResponseDto(Long id) { + this.id = id; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistEditTitleResponseDto.java b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistEditTitleResponseDto.java new file mode 100644 index 0000000..c9ca9bb --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistEditTitleResponseDto.java @@ -0,0 +1,17 @@ +package com.example.youtubedb.dto.playlist.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class PlaylistEditTitleResponseDto extends BaseResponseSuccessDto { + @Schema(description = "수정된 플레이리스트 아이디") + private final Long id; + @Schema(description = "수정 여부") + private final boolean edited = true; + + public PlaylistEditTitleResponseDto(Long id) { + this.id = id; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistGetResponseDto.java b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistGetResponseDto.java new file mode 100644 index 0000000..b78403f --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/playlist/response/PlaylistGetResponseDto.java @@ -0,0 +1,18 @@ +package com.example.youtubedb.dto.playlist.response; + +import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.util.List; + +@Getter +public class PlaylistGetResponseDto extends BaseResponseSuccessDto { + @Schema(description = "플레이리스트 목록") + private final List response; + + public PlaylistGetResponseDto(List response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index bab40f8..28c9534 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -1,7 +1,10 @@ package com.example.youtubedb.exception; -import com.example.youtubedb.dto.ErrorDto; +import com.example.youtubedb.dto.BaseResponseFailDto; import com.example.youtubedb.dto.ResponseDto; +import com.example.youtubedb.dto.error.BadRequestFailResponseDto; +import com.example.youtubedb.dto.error.NotAcceptableFailResponseDto; +import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; import com.example.youtubedb.util.ResponseUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -21,7 +24,10 @@ public class ControllerExceptionHandler { InvalidSeqException.class }) public ResponseEntity badRequest(Exception e) { - ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()); + BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .message(e.getMessage()) + .build(); return ResponseEntity.badRequest().body(responseBody); } @@ -30,7 +36,10 @@ public ResponseEntity badRequest(Exception e) { InvalidAccessException.class }) public ResponseEntity notAcceptable(Exception e) { - ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.NOT_ACCEPTABLE.value()); + BaseResponseFailDto responseBody = NotAcceptableFailResponseDto.builder() + .status(HttpStatus.NOT_ACCEPTABLE.value()) + .message(e.getMessage()) + .build(); return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(responseBody); } @@ -39,7 +48,10 @@ public ResponseEntity notAcceptable(Exception e) { RuntimeException.class }) public ResponseEntity serverError(Exception e) { - ResponseDto responseBody = ResponseUtil.getFailResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value()); + BaseResponseFailDto responseBody = ServerErrorFailResponseDto.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .message(e.getMessage()) + .build(); return ResponseEntity.internalServerError().body(responseBody); } diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index 8d91755..2942d6c 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -35,7 +35,7 @@ public Play addPlayToPlaylist( String title, String channelAvatar) { checkTime(start, end); - RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); +// RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); int sequence = playlist.getPlays().size() + 1; @@ -61,7 +61,7 @@ private void checkTime(Long start, Long end) { } public List getPlaysInPlaylist(Playlist playlist, String loginId) { - validateWatch(playlist, loginId); +// validateWatch(playlist, loginId); return playlist.getPlays(); } @@ -73,7 +73,7 @@ private void validateWatch(Playlist playlist, String loginId) { } public void deletePlayById(Play play, String loginId) { - RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); +// RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); playRepository.deleteById(play.getId()); } @@ -82,7 +82,7 @@ public Play getPlayById(Long id) { } public void editTime(Play play, String loginId, long start, long end) { - RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); +// RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); checkTime(start, end); play.setTime(start, end); playRepository.save(play); @@ -93,7 +93,7 @@ public void editSeq(String loginId, Long playlistId, List seqList) { seqList.forEach(p -> { Play play = getPlayById(p.getId()); RequestUtil.checkOwn(playlistId, play.getPlaylist().getId()); - RequestUtil.checkOwn(loginId, play.getPlaylist().getMember().getLoginId()); +// RequestUtil.checkOwn(loginId, play.getPlaylist().getMember().getLoginId()); play.setSequence(p.getSequence()); }); } diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 82e3155..93f81a5 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -24,11 +24,11 @@ public PlaylistService(PlaylistRepository playlistRepository) { this.playlistRepository = playlistRepository; } - public Playlist createPlaylist(String title, String isPublic, String category, Member member) { + public Playlist createPlaylist(String title, Boolean isPublic, String category, Member member) { checkCategory(category); Playlist playlist = Playlist.builder() .title(title) - .isPublic(Boolean.parseBoolean(isPublic)) + .isPublic(isPublic) .category(Category.valueOf(category)) .build(); playlist.setMember(member); @@ -40,26 +40,24 @@ private void checkCategory(String category) { Category[] categories = Category.values(); int count = 0; if (Arrays.stream(categories).noneMatch(c -> c.toString().equals(category))) { + System.out.println(category); throw new NotExistRequestValueException(); } } - public Playlist editPlaylistTitle(String id, String title, String loginId) { - Long lId = Long.parseLong(id); - Playlist playlist = getPlaylistById(lId); - RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); - + public Playlist editPlaylistTitle(Long id, String title, String loginId) { + Playlist playlist = getPlaylistById(id); +// RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); playlist.setTitle(title); return playlist; } - public void deletePlaylistById(String id, String loginId) { - Long lId = Long.parseLong(id); - Playlist playlist = getPlaylistById(lId); - RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); + public void deletePlaylistById(Long id, String loginId) { + Playlist playlist = getPlaylistById(id); +// RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); - playlistRepository.deleteById(lId); + playlistRepository.deleteById(id); } public Playlist getPlaylistById(Long lId) { diff --git a/src/main/java/com/example/youtubedb/util/RequestUtil.java b/src/main/java/com/example/youtubedb/util/RequestUtil.java index 78c3d04..bcd51e9 100644 --- a/src/main/java/com/example/youtubedb/util/RequestUtil.java +++ b/src/main/java/com/example/youtubedb/util/RequestUtil.java @@ -3,12 +3,12 @@ import com.example.youtubedb.exception.InvalidAccessException; import com.example.youtubedb.exception.NotExistRequestValueException; -import java.util.List; - public class RequestUtil { - public static void checkNeedValue(Object ...args) { - for(Object arg : args) { - if(arg == null) throw new NotExistRequestValueException(); + public static void checkNeedValue(Object... args) { + for (Object arg : args) { + if (arg == null) { + throw new NotExistRequestValueException(); + } } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d023149..e724434 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -46,4 +46,4 @@ management: custom: include: diskSpace, ping show-components: always - show-details: always + show-details: always \ No newline at end of file diff --git a/src/main/resources/swagger.yaml b/src/main/resources/swagger.yaml new file mode 100644 index 0000000..0801768 --- /dev/null +++ b/src/main/resources/swagger.yaml @@ -0,0 +1,29 @@ +openapi: 3.0.0 +info: + version: '1.0.0' + title: '개발이 취미인 남자' + description: '프로젝트 설명 RestFul Api 클라이언트 UI 로컬 서버를 구동 후 요청해주세요.' + +#내가 요청하고 싶은 서버 url 설정 다수로 설정가능 +servers: + - description: "프로젝트 Dev Server" + url: http://localhost:8080/api + +# API 요청 경로 및 데이터 세팅 +paths: + /: + get: + summary: "Get 방식(1)" + description: "서버에 아무 데이터 업이 Get방식으로 요청" + tags: + - Get 방식 + responses: + '200': + description: 서버에게 받은 결과 값 + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index aa5e4a4..ba3030f 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -49,7 +49,7 @@ void setup() { void 플레이_추가() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); // when Play play = playService.addPlayToPlaylist( @@ -79,7 +79,7 @@ void setup() { void 플레이_추가_시간예외() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); // when Exception e = assertThrows(StartAndEndTimeException.class , () -> @@ -102,7 +102,7 @@ void setup() { void 영상목록_조회() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -123,34 +123,34 @@ void setup() { ); } - @Test - void 영상목록_조회_접근권한X() { - // given - Member member = memberService.registerNon("device001"); - Member other = memberService.registerNon("device002"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); - playService.addPlayToPlaylist( - playlist, - member.getLoginId(), - videoId, - start, - end, - thumbnail, - title, - channelAvatar); - - // when - Exception e = assertThrows(InvalidAccessException.class, () -> playService.getPlaysInPlaylist(playlist, other.getLoginId())); - - // then - assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); - } +// @Test +// void 영상목록_조회_접근권한X() { +// // given +// Member member = memberService.registerNon("device001"); +// Member other = memberService.registerNon("device002"); +// Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); +// playService.addPlayToPlaylist( +// playlist, +// member.getLoginId(), +// videoId, +// start, +// end, +// thumbnail, +// title, +// channelAvatar); +// +// // when +// Exception e = assertThrows(InvalidAccessException.class, () -> playService.getPlaysInPlaylist(playlist, other.getLoginId())); +// +// // then +// assertThat(e.getMessage()).isEqualTo(InvalidAccessException.getErrorMessage()); +// } @Test void 영상_시간_수정() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -176,7 +176,7 @@ void setup() { void 영상_순서_수정() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -224,7 +224,7 @@ void setup() { void 영상_순서_수정_순서이상() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -268,7 +268,7 @@ void setup() { void 영상_순서_수정_중복() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -312,7 +312,7 @@ void setup() { void 영상_삭제() { // given Member member = memberService.registerNon("device001"); - Playlist playlist = playlistService.createPlaylist("default", "false", "OTHER", member); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play = playService.addPlayToPlaylist( playlist, member.getLoginId(), diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index ce33eda..9bd5fee 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -3,7 +3,6 @@ import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.Category; -import com.example.youtubedb.exception.InvalidAccessException; import com.example.youtubedb.exception.NotExistPlaylistException; import com.example.youtubedb.exception.NotExistRequestValueException; import org.junit.jupiter.api.Test; @@ -30,7 +29,7 @@ class PlaylistServiceIntegrationTest { // given Member member = memberService.registerNon("device001"); String title = "myList"; - String isPublic = "true"; + Boolean isPublic = true; String category = "GAME"; // when @@ -51,7 +50,7 @@ class PlaylistServiceIntegrationTest { // given Member member = memberService.registerNon("device001"); String title = "myList"; - String isPublic = "true"; + Boolean isPublic = true; String category = "???"; // when @@ -66,7 +65,7 @@ class PlaylistServiceIntegrationTest { // given Member member = memberService.registerNon("devide001"); String title = "myList"; - String isPublic = "false"; + Boolean isPublic = false; String category = "GAME"; Playlist playlist = playlistService.createPlaylist(title, isPublic, category, member); @@ -77,7 +76,7 @@ class PlaylistServiceIntegrationTest { assertAll( () -> assertThat(result.getId()).isEqualTo(playlist.getId()), () -> assertThat(result.getTitle()).isEqualTo(title), - () -> assertThat(result.isPublic()).isEqualTo(Boolean.parseBoolean(isPublic)), + () -> assertThat(result.isPublic()).isEqualTo(isPublic), () -> assertThat(result.getCategory()).isEqualTo(Category.valueOf(category)), () -> assertThat(result.getMember().getId()).isEqualTo(member.getId()) ); @@ -87,10 +86,10 @@ class PlaylistServiceIntegrationTest { void 플레이리스트_수정() { // given Member member = memberService.registerNon("devide001"); - Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); String newTitle = "newList"; // when - Playlist newList = playlistService.editPlaylistTitle(playlist.getId().toString(), newTitle, member.getLoginId()); + Playlist newList = playlistService.editPlaylistTitle(playlist.getId(), newTitle, member.getLoginId()); // then assertThat(newList.getTitle()).isEqualTo(newTitle); @@ -100,7 +99,7 @@ class PlaylistServiceIntegrationTest { void 플레이리스트_존재X() { // given Member member = memberService.registerNon("devide001"); - Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); // when Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.getPlaylistById(100L)); @@ -111,11 +110,11 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_삭제() { // given - Member member = memberService.registerNon("devide001"); - Playlist playlist = playlistService.createPlaylist("myList", "false", "GAME", member); + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); // when - playlistService.deletePlaylistById(playlist.getId().toString(), member.getLoginId()); + playlistService.deletePlaylistById(playlist.getId(), member.getLoginId()); Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.getPlaylistById(playlist.getId()) ); From 3505c2f87ae1351fe1b9711a8db9fbf4a41591bb Mon Sep 17 00:00:00 2001 From: oh980225 Date: Sat, 31 Jul 2021 17:56:03 +0900 Subject: [PATCH 09/46] =?UTF-8?q?[FIX]=20=EC=82=AD=EC=A0=9C=EC=8B=9C=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EC=9E=AC=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/controller/PlayController.java | 2 +- .../youtubedb/service/PlayService.java | 15 +++++++ .../service/PlayServiceIntegrationTest.java | 42 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index 4af3ee4..89c7683 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -200,7 +200,7 @@ public ResponseEntity deletePlay(@Parameter @PathVariable("id") Long id) { Play play = playService.getPlayById(id); playService.deletePlayById(play, "loginId?"); - + playService.sortPlaysInPlaylist(play.getPlaylist()); BaseResponseSuccessDto responseBody = new PlayDeleteResponseDto(id); return ResponseEntity.ok(responseBody); diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index 2942d6c..9c5ade1 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -13,6 +13,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; @Service @@ -117,4 +118,18 @@ private void checkSeqNum(int sequence, int size) { throw new InvalidSeqException(); } } + + public void sortPlaysInPlaylist(Playlist playlist) { + List plays = playlist.getPlays(); + plays.sort(new Comparator() { + @Override + public int compare(Play p1, Play p2) { + return p1.getSequence() - p2.getSequence(); + } + }); + int seq = 1; + for (Play play : plays) { + play.setSequence(seq++); + } + } } diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index ba3030f..7ec302b 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -330,4 +330,46 @@ void setup() { // then assertThat(e.getMessage()).isEqualTo(NotExistPlayException.getErrorMessage()); } + + @Test + void 영상_재정렬() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); + Play play1 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + + Play play2 = playService.addPlayToPlaylist( + playlist, + member.getLoginId(), + videoId, + start, + end, + thumbnail, + title, + channelAvatar); + play2.setSequence(3); +// Play play3 = playService.addPlayToPlaylist( +// playlist, +// member.getLoginId(), +// videoId, +// start, +// end, +// thumbnail, +// title, +// channelAvatar); + // when + playService.sortPlaysInPlaylist(playlist); + + // then + List plays = playlist.getPlays(); + assertThat(play2.getSequence()).isEqualTo(2); + } } From 1bd5399ac0b6f0f1ebd0025dcce28f2d7e98c243 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 2 Aug 2021 09:14:40 +0900 Subject: [PATCH 10/46] =?UTF-8?q?[ADD]=20RDS=20MySQL=20DB=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + build.gradle | 3 ++- src/main/resources/application-local.yaml | 7 +++++++ src/main/resources/application.yaml | 14 ++++---------- src/main/resources/import.sql | 7 ++++--- 5 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 src/main/resources/application-local.yaml diff --git a/.gitignore b/.gitignore index c2065bc..402406d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +application-prod.yaml ### STS ### .apt_generated diff --git a/build.gradle b/build.gradle index 88341e1..7e3c5b3 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'com.h2database:h2' +// runtimeOnly 'com.h2database:h2' + runtimeOnly 'mysql:mysql-connector-java' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-actuator' diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml new file mode 100644 index 0000000..0a2dfe4 --- /dev/null +++ b/src/main/resources/application-local.yaml @@ -0,0 +1,7 @@ +spring: + datasource: + # url: jdbc:h2:tcp://localhost/~/spring + url: jdbc:h2:mem:testdb + username: sa + password: + driver-class-name: org.h2.Driver \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e724434..a2a6378 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,23 +2,17 @@ server: port: 8080 spring: - datasource: -# url: jdbc:h2:tcp://localhost/~/spring - url: jdbc:h2:mem:testdb - username: sa - password: - driver-class-name: org.h2.Driver + profiles: + active: prod boot: admin: client: url: http://localhost:8080 - - jpa: hibernate: - ddl-auto: create + ddl-auto: none properties: hibernate: show_sql: true @@ -46,4 +40,4 @@ management: custom: include: diskSpace, ping show-components: always - show-details: always \ No newline at end of file + show-details: always diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 56518c3..1c23c9b 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,3 +1,4 @@ -insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); -insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); -insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); \ No newline at end of file +-- insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); +-- insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); +-- insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); +-- 테스트 데이터 (H2) \ No newline at end of file From f70ffcf9be4d3c773af4b26eed64f4d438dc7a54 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 2 Aug 2021 10:25:59 +0900 Subject: [PATCH 11/46] =?UTF-8?q?[FIX]=20=EC=8B=9C=EA=B0=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20FIX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/youtubedb/controller/PlayController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index 89c7683..035c836 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -140,7 +140,7 @@ public ResponseEntity editPlayTime(@RequestBody PlayEditTimeRequestDto reques play, "loginId?", request.getStart(), - play.getEnd()); + request.getEnd()); BaseResponseSuccessDto responseBody = new PlayEditTimeResponseDto(request.getId()); From 6ed11520cee5bcecf2e3f2cde1258bde13a62dc0 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 2 Aug 2021 14:13:52 +0900 Subject: [PATCH 12/46] =?UTF-8?q?[ADD]=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=9D=98=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaylistController.java | 1 + .../example/youtubedb/domain/Playlist.java | 4 +++ .../youtubedb/service/MemberService.java | 1 + .../youtubedb/service/PlaylistService.java | 18 ++++++++++++ .../PlaylistServiceIntegrationTest.java | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+) diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index e6d7011..871cae8 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -63,6 +63,7 @@ public PlaylistController(PlaylistService playlistService, MemberService memberS public ResponseEntity getPlaylist(@Parameter @PathVariable("loginId") String loginId) { Member member = memberService.findMemberByLoginId(loginId); List playlists = member.getPlaylists(); + playlistService.addThumbnail(playlists); BaseResponseSuccessDto responseBody = new PlaylistGetResponseDto(playlists); diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index 123cb32..e98f000 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -29,6 +29,10 @@ public class Playlist extends BaseEntity { @OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL) private List plays = new ArrayList<>(); + @Transient + @Setter + private String thumbnail; + @Builder public Playlist(String title, boolean isPublic, Category category) { this.title = title; diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 472cb18..991590a 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -1,6 +1,7 @@ package com.example.youtubedb.service; import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 93f81a5..31a12ee 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -1,6 +1,7 @@ package com.example.youtubedb.service; import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.Category; import com.example.youtubedb.exception.InvalidAccessException; @@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; +import java.util.List; @Service @Transactional @@ -63,4 +65,20 @@ public void deletePlaylistById(Long id, String loginId) { public Playlist getPlaylistById(Long lId) { return playlistRepository.findById(lId).orElseThrow(NotExistPlaylistException::new); } + + public void addThumbnail(List playlists) { + playlists.forEach(playlist -> { + String thumbnail = getThumbnailInPlaylist(playlist.getPlays()); + playlist.setThumbnail(thumbnail); + }); + } + + private String getThumbnailInPlaylist(List plays) { + for (Play play : plays) { + if (play.getSequence() == 1) { + return play.getThumbnail(); + } + } + return null; + } } diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index 9bd5fee..7e76858 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -1,6 +1,7 @@ package com.example.youtubedb.service; import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.Category; import com.example.youtubedb.exception.NotExistPlaylistException; @@ -23,6 +24,8 @@ class PlaylistServiceIntegrationTest { PlaylistService playlistService; @Autowired MemberService memberService; + @Autowired + PlayService playService; @Test void 플레이리스트_생성() { @@ -122,4 +125,29 @@ class PlaylistServiceIntegrationTest { // then assertThat(e.getMessage()).isEqualTo(NotExistPlaylistException.getErrorMessage()); } + + @Test + void 썸네일_설정() { + // given + Member member = memberService.registerNon("device001"); + Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); + String thumbnail = "thumbnail"; + + // when + Play play = Play.builder() + .end(100L) + .start(20L) + .sequence(1) + .title("title") + .videoId("video001") + .thumbnail(thumbnail) + .channelAvatar("avatar") + .build(); + + play.setPlaylist(playlist); + playlistService.addThumbnail(member.getPlaylists()); + + // then + assertThat(playlist.getThumbnail()).isEqualTo(thumbnail); + } } \ No newline at end of file From 245a59e1a5bf23a3e16722c619e4d883f1c37870 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Tue, 3 Aug 2021 20:49:24 +0900 Subject: [PATCH 13/46] =?UTF-8?q?[ADD]=20CORS=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++++-- .../example/youtubedb/config/SpringConfig.java | 15 +++++++++++++++ src/main/resources/application-local.yaml | 4 ++++ src/main/resources/application.yaml | 6 +----- 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/config/SpringConfig.java diff --git a/build.gradle b/build.gradle index 85dad49..14a666c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,8 +22,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' developmentOnly 'org.springframework.boot:spring-boot-devtools' -// runtimeOnly 'com.h2database:h2' - runtimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'com.h2database:h2' +// runtimeOnly 'mysql:mysql-connector-java' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-actuator' @@ -32,6 +32,9 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' +// implementation "org.springframework.boot:spring-boot-starter-security" +// implementation 'org.springframework.security:spring-security-test' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/config/SpringConfig.java b/src/main/java/com/example/youtubedb/config/SpringConfig.java new file mode 100644 index 0000000..46c8034 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/SpringConfig.java @@ -0,0 +1,15 @@ +package com.example.youtubedb.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class SpringConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins("*") + .allowedMethods("*"); + } +} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 0a2dfe4..6d04037 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,4 +1,8 @@ spring: + jpa: + hibernate: + ddl-auto: create + datasource: # url: jdbc:h2:tcp://localhost/~/spring url: jdbc:h2:mem:testdb diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1c34aef..bcff844 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -3,17 +3,13 @@ server: spring: profiles: - active: prod + active: local boot: admin: client: url: http://localhost:8080 - jpa: - hibernate: - ddl-auto: none - properties: hibernate: show_sql: true From c630e9398d877695d7a12c927e7c5891075d504c Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 8 Aug 2021 00:42:09 +0900 Subject: [PATCH 14/46] [ADD]jwt --- .gitignore | 1 + build.gradle | 9 ++ .../example/youtubedb/config/TestConfig.java | 13 ++ .../youtubedb/config/WebSecurityConfig.java | 72 +++++++++++ .../config/jwt/JwtAccessDeniedHandler.java | 21 ++++ .../jwt/JwtAuthenticationEntryPoint.java | 20 ++++ .../youtubedb/config/jwt/JwtFilter.java | 48 ++++++++ .../config/jwt/JwtSecurityConfig.java | 19 +++ .../youtubedb/config/jwt/TokenProvider.java | 113 ++++++++++++++++++ .../youtubedb/controller/AuthController.java | 32 +++++ .../example/youtubedb/domain/Authority.java | 5 + .../com/example/youtubedb/domain/Member.java | 12 +- .../youtubedb/domain/RefreshToken.java | 31 +++++ .../dto/member/request/MemberRequestDto.java | 29 +++++ .../member/response/MemberResponseDto.java | 17 +++ .../example/youtubedb/dto/token/TokenDto.java | 18 +++ .../youtubedb/dto/token/TokenRequestDto.java | 11 ++ .../repository/MemberRepository.java | 1 + .../repository/RefreshTokenRepository.java | 12 ++ .../youtubedb/service/AuthService.java | 94 +++++++++++++++ .../service/CustomUserDetailsService.java | 39 ++++++ .../youtubedb/service/MemberService.java | 9 +- src/main/resources/application.yaml | 12 +- src/main/resources/templates/hello.html | 13 ++ src/main/resources/templates/login.html | 20 ++++ 25 files changed, 661 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/config/TestConfig.java create mode 100644 src/main/java/com/example/youtubedb/config/WebSecurityConfig.java create mode 100644 src/main/java/com/example/youtubedb/config/jwt/JwtAccessDeniedHandler.java create mode 100644 src/main/java/com/example/youtubedb/config/jwt/JwtAuthenticationEntryPoint.java create mode 100644 src/main/java/com/example/youtubedb/config/jwt/JwtFilter.java create mode 100644 src/main/java/com/example/youtubedb/config/jwt/JwtSecurityConfig.java create mode 100644 src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java create mode 100644 src/main/java/com/example/youtubedb/controller/AuthController.java create mode 100644 src/main/java/com/example/youtubedb/domain/Authority.java create mode 100644 src/main/java/com/example/youtubedb/domain/RefreshToken.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/token/TokenDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java create mode 100644 src/main/java/com/example/youtubedb/service/AuthService.java create mode 100644 src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java create mode 100644 src/main/resources/templates/hello.html create mode 100644 src/main/resources/templates/login.html diff --git a/.gitignore b/.gitignore index 402406d..18eeb13 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ application-prod.yaml +**/src/resources/application.yaml ### STS ### .apt_generated diff --git a/build.gradle b/build.gradle index 14a666c..dc7f1c1 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,15 @@ dependencies { // implementation "org.springframework.boot:spring-boot-starter-security" // implementation 'org.springframework.security:spring-security-test' + //security + implementation "org.springframework.boot:spring-boot-starter-security" + implementation 'org.springframework.security:spring-security-test' + + //jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/config/TestConfig.java b/src/main/java/com/example/youtubedb/config/TestConfig.java new file mode 100644 index 0000000..14ddbc6 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/TestConfig.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class TestConfig implements WebMvcConfigurer { + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/hello").setViewName("hello"); + registry.addViewController("/login").setViewName("login"); + } +} diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java new file mode 100644 index 0000000..a7a5431 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -0,0 +1,72 @@ +package com.example.youtubedb.config; + +import com.example.youtubedb.config.jwt.JwtAccessDeniedHandler; +import com.example.youtubedb.config.jwt.JwtAuthenticationEntryPoint; +import com.example.youtubedb.config.jwt.JwtSecurityConfig; +import com.example.youtubedb.config.jwt.TokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@RequiredArgsConstructor +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final TokenProvider tokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + + .exceptionHandling() + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler) + + .and() + .headers() + .frameOptions() + .sameOrigin() + + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and() + .authorizeRequests() + .antMatchers("/api/auth/**").permitAll() + .anyRequest().authenticated() + + .and() + .apply(new JwtSecurityConfig(tokenProvider)); + + + + +// .authorizeRequests() +// .antMatchers("/", "/applications").authenticated() +// .antMatchers("/admin").hasRole("ADMIN") +// .anyRequest().permitAll() +// .and() +// .formLogin() +// .loginPage("/login") +// .successForwardUrl("/") +// .permitAll() +// .and() +// .logout() +// .permitAll(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/youtubedb/config/jwt/JwtAccessDeniedHandler.java b/src/main/java/com/example/youtubedb/config/jwt/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..02260b9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/jwt/JwtAccessDeniedHandler.java @@ -0,0 +1,21 @@ +package com.example.youtubedb.config.jwt; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + // 필요한 권한이 없이 접근하려 할때 403 + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } +} diff --git a/src/main/java/com/example/youtubedb/config/jwt/JwtAuthenticationEntryPoint.java b/src/main/java/com/example/youtubedb/config/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..35ecb0a --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,20 @@ +package com.example.youtubedb.config.jwt; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/config/jwt/JwtFilter.java b/src/main/java/com/example/youtubedb/config/jwt/JwtFilter.java new file mode 100644 index 0000000..755a4cd --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/jwt/JwtFilter.java @@ -0,0 +1,48 @@ +package com.example.youtubedb.config.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtFilter extends OncePerRequestFilter { + + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String BEARER_PREFIX = "Bearer "; + + private final TokenProvider tokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + + // 1. Request Header 에서 토큰을 꺼냄 + String jwt = resolveToken(request); + + // 2. validateToken 으로 토큰 유효성 검사 + // 정상 토큰이면 해당 토큰으로 Authentication 을 가져와서 SecurityContext 에 저장 + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { + Authentication authentication = tokenProvider.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } + + // Request Header 에서 토큰 정보를 꺼내오기 + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/example/youtubedb/config/jwt/JwtSecurityConfig.java b/src/main/java/com/example/youtubedb/config/jwt/JwtSecurityConfig.java new file mode 100644 index 0000000..e30e577 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/jwt/JwtSecurityConfig.java @@ -0,0 +1,19 @@ +package com.example.youtubedb.config.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +public class JwtSecurityConfig extends SecurityConfigurerAdapter { + + private final TokenProvider tokenProvider; + + @Override + public void configure(HttpSecurity http) throws Exception { + JwtFilter customFilter = new JwtFilter(tokenProvider); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java new file mode 100644 index 0000000..4e9c56c --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -0,0 +1,113 @@ +package com.example.youtubedb.config.jwt; + +import com.example.youtubedb.dto.token.TokenDto; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class TokenProvider { + + private static final String AUTHORITIES_KEY = "auth"; + private static final String BEARER_TYPE = "bearer"; + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일 + + private final Key key; + + public TokenProvider(@Value("${jwt.secret}") String secretKey) { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + public TokenDto generateTokenDto(Authentication authentication) { + // 권한들 가져오기 + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + long now = (new Date()).getTime(); + + // Access Token 생성 + Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME); + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) // payload "sub": "name" + .claim(AUTHORITIES_KEY, authorities) // payload "auth": "ROLE_USER" + .setExpiration(accessTokenExpiresIn) // payload "exp": 1516239022 (예시) + .signWith(key, SignatureAlgorithm.HS512) // header "alg": "HS512" + .compact(); + + // Refresh Token 생성 + String refreshToken = Jwts.builder() + .setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME)) + .signWith(key, SignatureAlgorithm.HS512) + .compact(); + + return TokenDto.builder() + .grantType(BEARER_TYPE) + .accessToken(accessToken) + .accessTokenExpiresIn(accessTokenExpiresIn.getTime()) + .refreshToken(refreshToken) + .build(); + } + + public Authentication getAuthentication(String accessToken) { + // 토큰 복호화 + Claims claims = parseClaims(accessToken); + + if (claims.get(AUTHORITIES_KEY) == null) { + throw new RuntimeException("권한 정보가 없는 토큰입니다."); + } + + // 클레임에서 권한 정보 가져오기 + Collection authorities = + Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + // UserDetails 객체를 만들어서 Authentication 리턴 + UserDetails principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.info("잘못된 JWT 서명입니다."); + } catch (ExpiredJwtException e) { + log.info("만료된 JWT 토큰입니다."); + } catch (UnsupportedJwtException e) { + log.info("지원되지 않는 JWT 토큰입니다."); + } catch (IllegalArgumentException e) { + log.info("JWT 토큰이 잘못되었습니다."); + } + return false; + } + + private Claims parseClaims(String accessToken) { + try { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/controller/AuthController.java b/src/main/java/com/example/youtubedb/controller/AuthController.java new file mode 100644 index 0000000..f757cbe --- /dev/null +++ b/src/main/java/com/example/youtubedb/controller/AuthController.java @@ -0,0 +1,32 @@ +package com.example.youtubedb.controller; + +import com.example.youtubedb.dto.token.TokenDto; +import com.example.youtubedb.dto.member.request.MemberRequestDto; +import com.example.youtubedb.dto.member.response.MemberResponseDto; +import com.example.youtubedb.dto.token.TokenRequestDto; +import com.example.youtubedb.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + private final AuthService authService; + + @PostMapping("/signup") + public ResponseEntity signup(@RequestBody MemberRequestDto memberRequestDto) { + return ResponseEntity.ok(authService.signup(memberRequestDto)); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { + return ResponseEntity.ok(authService.login(memberRequestDto)); + } + + @PostMapping("/reissue") + public ResponseEntity reissue(@RequestBody TokenRequestDto tokenRequestDto) { + return ResponseEntity.ok(authService.reissue(tokenRequestDto)); + } +} diff --git a/src/main/java/com/example/youtubedb/domain/Authority.java b/src/main/java/com/example/youtubedb/domain/Authority.java new file mode 100644 index 0000000..b9dc205 --- /dev/null +++ b/src/main/java/com/example/youtubedb/domain/Authority.java @@ -0,0 +1,5 @@ +package com.example.youtubedb.domain; + +public enum Authority { + ROLE_USER, ROLE_ADMIN +} diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/Member.java index 8c0f0fa..0433cc3 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/Member.java @@ -17,14 +17,22 @@ public class Member extends BaseEntity { @JsonIgnore private String password; private boolean isMember; + + @Enumerated + private Authority authority; + + @JsonIgnore @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List playlists = new ArrayList<>(); @Builder - public Member(String loginId, String password, boolean isMember) { + public Member(String loginId, String password, boolean isMember, Authority authority){ this.loginId = loginId; this.password = password; this.isMember = isMember; + this.authority = authority; + } -} + +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/domain/RefreshToken.java b/src/main/java/com/example/youtubedb/domain/RefreshToken.java new file mode 100644 index 0000000..bd47cd0 --- /dev/null +++ b/src/main/java/com/example/youtubedb/domain/RefreshToken.java @@ -0,0 +1,31 @@ +package com.example.youtubedb.domain; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Getter +@NoArgsConstructor +@Entity +@Table(name = "refresh_token") +public class RefreshToken { + + @Id + private String key; + private String value; + + public RefreshToken updateValue(String token) { + this.value = token; + return this; + } + + @Builder + public RefreshToken(String key, String value) { + this.key = key; + this.value = value; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java new file mode 100644 index 0000000..0c9c56d --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java @@ -0,0 +1,29 @@ +package com.example.youtubedb.dto.member.request; + +import com.example.youtubedb.domain.Authority; +import com.example.youtubedb.domain.Member; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class MemberRequestDto { + private String loginId; + private String password; + + public Member toMember(PasswordEncoder passwordEncoder) { + return Member.builder() + .loginId(loginId) + .password(passwordEncoder.encode(password)) + .authority(Authority.ROLE_USER) + .build(); + } + + public UsernamePasswordAuthenticationToken toAuthentication() { + return new UsernamePasswordAuthenticationToken(loginId, password); + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java new file mode 100644 index 0000000..76cb402 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java @@ -0,0 +1,17 @@ +package com.example.youtubedb.dto.member.response; + +import com.example.youtubedb.domain.Member; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class MemberResponseDto { + private String loginId; + + public static MemberResponseDto of(Member member) { + return new MemberResponseDto(member.getLoginId()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/TokenDto.java b/src/main/java/com/example/youtubedb/dto/token/TokenDto.java new file mode 100644 index 0000000..1fb54e9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/token/TokenDto.java @@ -0,0 +1,18 @@ +package com.example.youtubedb.dto.token; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TokenDto { + + private String grantType; + private String accessToken; + private String refreshToken; + private Long accessTokenExpiresIn; +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java b/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java new file mode 100644 index 0000000..71a2f8a --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java @@ -0,0 +1,11 @@ +package com.example.youtubedb.dto.token; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TokenRequestDto { + private String accessToken; + private String refreshToken; +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/repository/MemberRepository.java b/src/main/java/com/example/youtubedb/repository/MemberRepository.java index b79b0d7..d9cccbb 100644 --- a/src/main/java/com/example/youtubedb/repository/MemberRepository.java +++ b/src/main/java/com/example/youtubedb/repository/MemberRepository.java @@ -8,4 +8,5 @@ public interface MemberRepository { Member save(Member member); Optional findByLoginId(String loginId); + boolean existsByLoginId(String loginId); } diff --git a/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java b/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..f11e919 --- /dev/null +++ b/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java @@ -0,0 +1,12 @@ +package com.example.youtubedb.repository; + +import com.example.youtubedb.domain.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RefreshTokenRepository extends JpaRepository { + Optional findByKey(String key); +} diff --git a/src/main/java/com/example/youtubedb/service/AuthService.java b/src/main/java/com/example/youtubedb/service/AuthService.java new file mode 100644 index 0000000..c5c8364 --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/AuthService.java @@ -0,0 +1,94 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.config.jwt.TokenProvider; +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.RefreshToken; +import com.example.youtubedb.dto.member.request.MemberRequestDto; +import com.example.youtubedb.dto.member.response.MemberResponseDto; +import com.example.youtubedb.dto.token.TokenDto; +import com.example.youtubedb.dto.token.TokenRequestDto; +import com.example.youtubedb.repository.MemberRepository; +import com.example.youtubedb.repository.RefreshTokenRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final TokenProvider tokenProvider; + private final RefreshTokenRepository refreshTokenRepository; + + @Transactional + public MemberResponseDto signup(MemberRequestDto memberRequestDto) { + if (memberRepository.existsByLoginId(memberRequestDto.getLoginId())) { + throw new RuntimeException("이미 가입되어 있는 유저입니다"); + } + + Member member = memberRequestDto.toMember(passwordEncoder); + return MemberResponseDto.of(memberRepository.save(member)); + } + + @Transactional + public TokenDto login(MemberRequestDto memberRequestDto) { + // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 + UsernamePasswordAuthenticationToken authenticationToken = memberRequestDto.toAuthentication(); + + // 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분 + // authenticate 메서드가 실행이 될 때 CustomUserDetailsService 에서 만들었던 loadUserByUsername 메서드가 실행됨 + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + + // 3. 인증 정보를 기반으로 JWT 토큰 생성 + TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); + + // 4. RefreshToken 저장 + RefreshToken refreshToken = RefreshToken.builder() + .key(authentication.getName()) + .value(tokenDto.getRefreshToken()) + .build(); + + refreshTokenRepository.save(refreshToken); + + // 5. 토큰 발급 + return tokenDto; + } + + @Transactional + public TokenDto reissue(TokenRequestDto tokenRequestDto) { + // 1. Refresh Token 검증 + if (!tokenProvider.validateToken(tokenRequestDto.getRefreshToken())) { + throw new RuntimeException("Refresh Token 이 유효하지 않습니다."); + } + + // 2. Access Token 에서 Member ID 가져오기 + Authentication authentication = tokenProvider.getAuthentication(tokenRequestDto.getAccessToken()); + + // 3. 저장소에서 Member ID 를 기반으로 Refresh Token 값 가져옴 + RefreshToken refreshToken = refreshTokenRepository.findByKey(authentication.getName()) + .orElseThrow(() -> new RuntimeException("로그아웃 된 사용자입니다.")); + + // 4. Refresh Token 일치하는지 검사 + if (!refreshToken.getValue().equals(tokenRequestDto.getRefreshToken())) { + throw new RuntimeException("토큰의 유저 정보가 일치하지 않습니다."); + } + + // 5. 새로운 토큰 생성 + TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); + + // 6. 저장소 정보 업데이트 + RefreshToken newRefreshToken = refreshToken.updateValue(tokenDto.getRefreshToken()); + refreshTokenRepository.save(newRefreshToken); + + // 토큰 발급 + return tokenDto; + } + +} diff --git a/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java new file mode 100644 index 0000000..2372edc --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java @@ -0,0 +1,39 @@ +package com.example.youtubedb.service; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + System.out.println("CustomUserDetailsService.loadUserByUsername"); + return memberRepository.findByLoginId(username) + .map(this::createUserDetails) + .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다.")); + } + private UserDetails createUserDetails(Member member) { + System.out.println("CustomUserDetailsService.createUserDetails"); + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(member.getAuthority().toString()); + + return new User( + String.valueOf(member.getId()), + member.getPassword(), + Collections.singleton(grantedAuthority) + ); + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 991590a..1694775 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -5,7 +5,7 @@ import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; -import com.example.youtubedb.util.RequestUtil; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,16 +15,11 @@ import java.util.Optional; @Service +@RequiredArgsConstructor @Transactional public class MemberService { private final MemberRepository memberRepository; - @Autowired - public MemberService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - public Member registerNon(String deviceId) { checkDuplicateMember(deviceId); Member nonMember = Member.builder() diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index bcff844..e4eec1a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -5,6 +5,14 @@ spring: profiles: active: local +# security 설정 + security: + user: + name: "admin" + password: "pass" + roles: "ADMIN" + + boot: admin: client: @@ -19,6 +27,8 @@ logging: level: org.hibernate.SQL: debug +jwt: + secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXR1dG9yaWFsLWppd29vbi1zcHJpbmctYm9vdC1zZWN1cml0eS1qd3QtdHV0b3JpYWwK management: endpoints: @@ -27,7 +37,7 @@ management: include: "*" # include: health, metrics - base-path: "/monitoring" + base-path: "/admin/monitoring" endpoint: health: diff --git a/src/main/resources/templates/hello.html b/src/main/resources/templates/hello.html new file mode 100644 index 0000000..e99ff84 --- /dev/null +++ b/src/main/resources/templates/hello.html @@ -0,0 +1,13 @@ + + + + Hello World! + + +

Hello [[${#httpServletRequest.remoteUser}]]!

+
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..ae6e698 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,20 @@ + + + + Spring Security Example + + +
+ Invalid username and password. +
+
+ You have been logged out. +
+
+
+
+
+
+ + \ No newline at end of file From ac3a6e391094332bb85c1b15590cace3e6cf3ce5 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Sun, 8 Aug 2021 20:01:10 +0900 Subject: [PATCH 15/46] =?UTF-8?q?[ADD]=203=EC=B0=A8=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A6=B0=ED=8A=B8=20Spring=20Security=20-=20JWT=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +- .../auth/AccessDeniedHandlerImpl.java | 29 ++++ .../auth/AuthenticationEntryPointImpl.java | 29 ++++ .../auth/JwtAuthenticationFilter.java | 32 +++++ .../youtubedb/auth/JwtTokenProvider.java | 88 ++++++++++++ .../youtubedb/config/MultiSecurityConfig.java | 90 ++++++++++++ .../youtubedb/config/SecurityConfig.java | 56 ++++++++ .../youtubedb/config/SpringConfig.java | 17 ++- .../controller/MemberController.java | 135 ++++++++++++++++-- .../youtubedb/controller/PlayController.java | 87 ++++++----- .../controller/PlaylistController.java | 57 +++++--- .../youtubedb/controller/TestController.java | 20 +++ .../com/example/youtubedb/domain/Member.java | 48 ++++++- .../com/example/youtubedb/domain/Play.java | 7 +- .../example/youtubedb/domain/Playlist.java | 7 +- .../dto/error/AccessDenyFailResponseDto.java | 20 +++ ...thenticationEntryPointFailResponseDto.java | 19 +++ .../dto/error/BadRequestFailResponseDto.java | 6 +- .../error/NotAcceptableFailResponseDto.java | 6 +- .../dto/error/ServerErrorFailResponseDto.java | 6 +- .../dto/member/request/MemberRequestDto.java | 24 ++++ ...questDto.java => NonMemberRequestDto.java} | 11 +- .../member/response/MemberResponseDto.java | 19 +++ ...onseDto.java => NonMemberResponseDto.java} | 7 +- .../play/request/PlayCreateRequestDto.java | 15 +- .../play/request/PlayEditSeqRequestDto.java | 5 +- .../play/request/PlayEditTimeRequestDto.java | 7 +- .../request/PlaylistCreateRequestDto.java | 12 +- .../request/PlaylistEditTitleRequestDto.java | 5 +- .../exception/ControllerExceptionHandler.java | 7 +- .../DoNotMatchPasswordException.java | 13 ++ .../repository/MemberRepository.java | 1 + .../youtubedb/repository/PlayRepository.java | 2 +- .../repository/PlaylistRepository.java | 2 +- .../youtubedb/service/MemberService.java | 55 +++++-- .../youtubedb/service/PlayService.java | 12 +- .../youtubedb/service/PlaylistService.java | 4 +- src/main/resources/application-local.yaml | 7 +- src/main/resources/import.sql | 4 +- .../service/MemberServiceIntegrationTest.java | 73 +++++++++- .../PlaylistServiceIntegrationTest.java | 2 - 41 files changed, 895 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/auth/AccessDeniedHandlerImpl.java create mode 100644 src/main/java/com/example/youtubedb/auth/AuthenticationEntryPointImpl.java create mode 100644 src/main/java/com/example/youtubedb/auth/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/example/youtubedb/auth/JwtTokenProvider.java create mode 100644 src/main/java/com/example/youtubedb/config/MultiSecurityConfig.java create mode 100644 src/main/java/com/example/youtubedb/config/SecurityConfig.java create mode 100644 src/main/java/com/example/youtubedb/controller/TestController.java create mode 100644 src/main/java/com/example/youtubedb/dto/error/AccessDenyFailResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/error/AuthenticationEntryPointFailResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java rename src/main/java/com/example/youtubedb/dto/member/request/{NonMemberCreateRequestDto.java => NonMemberRequestDto.java} (52%) create mode 100644 src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java rename src/main/java/com/example/youtubedb/dto/member/response/{NonMemberCreateResponseDto.java => NonMemberResponseDto.java} (60%) create mode 100644 src/main/java/com/example/youtubedb/exception/DoNotMatchPasswordException.java diff --git a/build.gradle b/build.gradle index 14a666c..b018467 100644 --- a/build.gradle +++ b/build.gradle @@ -32,8 +32,9 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' -// implementation "org.springframework.boot:spring-boot-starter-security" -// implementation 'org.springframework.security:spring-security-test' + implementation "org.springframework.boot:spring-boot-starter-security" + implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' + implementation 'org.springframework.security:spring-security-test' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/auth/AccessDeniedHandlerImpl.java b/src/main/java/com/example/youtubedb/auth/AccessDeniedHandlerImpl.java new file mode 100644 index 0000000..edf2264 --- /dev/null +++ b/src/main/java/com/example/youtubedb/auth/AccessDeniedHandlerImpl.java @@ -0,0 +1,29 @@ +package com.example.youtubedb.auth; + +import com.example.youtubedb.dto.error.AuthenticationEntryPointFailResponseDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class AccessDeniedHandlerImpl implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + ObjectMapper mapper = new ObjectMapper(); + response.getWriter().write( + mapper.writeValueAsString(AuthenticationEntryPointFailResponseDto.builder() + .status(HttpStatus.FORBIDDEN.value()) + .message(accessDeniedException.getMessage()) + .build()) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/auth/AuthenticationEntryPointImpl.java b/src/main/java/com/example/youtubedb/auth/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..ee50476 --- /dev/null +++ b/src/main/java/com/example/youtubedb/auth/AuthenticationEntryPointImpl.java @@ -0,0 +1,29 @@ +package com.example.youtubedb.auth; + +import com.example.youtubedb.dto.error.AuthenticationEntryPointFailResponseDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + ObjectMapper mapper = new ObjectMapper(); + response.getWriter().write( + mapper.writeValueAsString(AuthenticationEntryPointFailResponseDto.builder() + .status(HttpStatus.UNAUTHORIZED.value()) + .message(authException.getMessage()) + .build()) + ); + } +} diff --git a/src/main/java/com/example/youtubedb/auth/JwtAuthenticationFilter.java b/src/main/java/com/example/youtubedb/auth/JwtAuthenticationFilter.java new file mode 100644 index 0000000..ac25243 --- /dev/null +++ b/src/main/java/com/example/youtubedb/auth/JwtAuthenticationFilter.java @@ -0,0 +1,32 @@ +package com.example.youtubedb.auth; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Slf4j +public class JwtAuthenticationFilter extends GenericFilter { + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String token = jwtTokenProvider.resolveToken((HttpServletRequest) request); + if (token != null && jwtTokenProvider.validateToken(token)) { + try { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (Exception e) { + log.error(e.getMessage()); + } + } + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/auth/JwtTokenProvider.java b/src/main/java/com/example/youtubedb/auth/JwtTokenProvider.java new file mode 100644 index 0000000..1f74c1b --- /dev/null +++ b/src/main/java/com/example/youtubedb/auth/JwtTokenProvider.java @@ -0,0 +1,88 @@ +package com.example.youtubedb.auth; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.service.MemberService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.util.Base64; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + private final MemberService memberService; + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.time}") + private Long tokenValidTime; + + @PostConstruct + protected void init() { + System.out.println("SecretKey = " + secretKey); + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); + System.out.println("Post secretKey = " + secretKey); + } + + public String createToken(String userPk, String role, Boolean isPC) { + Claims claims = Jwts.claims().setSubject(userPk); + claims.put("roles", role); + Date now = new Date(); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + checkPC(isPC))) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } + + private long checkPC(Boolean isPC) { + long tokenTime = tokenValidTime; + if (isPC) { + tokenTime /= 2; + } + System.out.println("tokenValidTime = " + tokenValidTime); + System.out.println("realTokenTime = " + tokenTime); + return tokenTime; + } + + public Authentication getAuthentication(String token) { + Member member = memberService.findMemberByLoginId(this.getUserPk(token)); + + return new UsernamePasswordAuthenticationToken(member, "", member.getAuthorities()); + } + + public String getUserPk(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + + public void abandonToken(String token) { + Date now = new Date(); + System.out.println(now); + System.out.println(validateToken(token)); + } + + public String resolveToken(HttpServletRequest request) { + return request.getHeader("X-AUTH-TOKEN"); + } + + public boolean validateToken(String jwtToken) { + try { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); + return !claims.getBody().getExpiration().before(new Date()); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/example/youtubedb/config/MultiSecurityConfig.java b/src/main/java/com/example/youtubedb/config/MultiSecurityConfig.java new file mode 100644 index 0000000..475ea94 --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/MultiSecurityConfig.java @@ -0,0 +1,90 @@ +/* +package com.example.youtubedb.config; + +import com.example.youtubedb.auth.JwtAuthenticationFilter; +import com.example.youtubedb.auth.JwtTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +public class MultiSecurityConfig { + + @Configuration + @Order(1) + public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + + private final JwtTokenProvider jwtTokenProvider; + + @Autowired + public ApiWebSecurityConfigurationAdapter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + protected void configure(HttpSecurity http) throws Exception { + http + .headers().frameOptions().disable() // h2 콘솔때문에 + .and() + .httpBasic().disable() + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/member/**").permitAll() + .antMatchers("/api/**").hasAnyRole("USER", "ADMIN") + .antMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().permitAll() + .and() + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), + UsernamePasswordAuthenticationFilter.class); + } + } + + @Configuration + public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .headers().frameOptions().disable() // h2 콘솔때문에 + .and() + .httpBasic().disable() + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/member/**").permitAll() + .antMatchers("/api/**").hasAnyRole("USER", "ADMIN") + .antMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().permitAll() + .and() + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), + UsernamePasswordAuthenticationFilter.class).formLogin(); + + + } + } +}*/ + + +// 그냥 테스트 페이지 무시 ㄱ diff --git a/src/main/java/com/example/youtubedb/config/SecurityConfig.java b/src/main/java/com/example/youtubedb/config/SecurityConfig.java new file mode 100644 index 0000000..4f878bf --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/SecurityConfig.java @@ -0,0 +1,56 @@ +package com.example.youtubedb.config; + +import com.example.youtubedb.auth.JwtAuthenticationFilter; +import com.example.youtubedb.auth.JwtTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final JwtTokenProvider jwtTokenProvider; + private final AuthenticationEntryPoint authenticationEntryPoint; + private final AccessDeniedHandler accessDeniedHandler; + + @Autowired + public SecurityConfig(JwtTokenProvider jwtTokenProvider, AuthenticationEntryPoint authenticationEntryPoint, AccessDeniedHandler accessDeniedHandler) { + this.jwtTokenProvider = jwtTokenProvider; + this.authenticationEntryPoint = authenticationEntryPoint; + this.accessDeniedHandler = accessDeniedHandler; + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .headers().frameOptions().disable() // h2 콘솔때문에 + .and() + .httpBasic().disable() + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/member/**").permitAll() + .antMatchers("/api/**").hasAnyRole("USER", "ADMIN") + .antMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().permitAll() + .and() + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), + UsernamePasswordAuthenticationFilter.class) + .exceptionHandling() + .authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(accessDeniedHandler); + } +} diff --git a/src/main/java/com/example/youtubedb/config/SpringConfig.java b/src/main/java/com/example/youtubedb/config/SpringConfig.java index 46c8034..683153c 100644 --- a/src/main/java/com/example/youtubedb/config/SpringConfig.java +++ b/src/main/java/com/example/youtubedb/config/SpringConfig.java @@ -1,14 +1,29 @@ package com.example.youtubedb.config; +import com.example.youtubedb.auth.AuthenticationEntryPointImpl; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class SpringConfig implements WebMvcConfigurer { + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + +// @Bean +// public AuthenticationEntryPoint authenticationEntryPoint() { +// return new AuthenticationEntryPointImpl(); +// } + @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") + registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*"); } diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 166efe5..5ede405 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -1,11 +1,14 @@ package com.example.youtubedb.controller; +import com.example.youtubedb.auth.JwtTokenProvider; import com.example.youtubedb.domain.Member; import com.example.youtubedb.dto.BaseResponseSuccessDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; -import com.example.youtubedb.dto.member.request.NonMemberCreateRequestDto; -import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; +import com.example.youtubedb.dto.member.request.MemberRequestDto; +import com.example.youtubedb.dto.member.request.NonMemberRequestDto; +import com.example.youtubedb.dto.member.response.MemberResponseDto; +import com.example.youtubedb.dto.member.response.NonMemberResponseDto; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; @@ -17,10 +20,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -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 org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +// TODO 비회원 로그인 로직 필요 +// TODO actuator 관련 이슈 해결 필요! +// TODO 404에러 관리할 수 있으면 좋을듯 @Tag(name = "회원 관련 API") @RestController @@ -29,17 +35,21 @@ public class MemberController { private final MemberService memberService; private final PlaylistService playlistService; + private final JwtTokenProvider jwtTokenProvider; @Autowired - public MemberController(MemberService memberService, PlaylistService playlistService) { + public MemberController(MemberService memberService, + PlaylistService playlistService, + JwtTokenProvider jwtTokenProvider) { this.memberService = memberService; this.playlistService = playlistService; + this.jwtTokenProvider = jwtTokenProvider; } @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "비회원 생성 성공", - content = @Content(schema = @Schema(implementation = NonMemberCreateResponseDto.class))), + content = @Content(schema = @Schema(implementation = NonMemberResponseDto.class))), @ApiResponse(responseCode = "400", description = "* 잘못된 요청\n" + "1. 중복된 아이디 존재\n" + @@ -51,13 +61,114 @@ public MemberController(MemberService memberService, PlaylistService playlistSer }) @Operation(summary = "가입", description = "비회원 가입") @PostMapping("/register/non") - public ResponseEntity registerNonMember(@RequestBody NonMemberCreateRequestDto request) { - RequestUtil.checkNeedValue(request.getDeviceId()); - Member nonMember = memberService.registerNon(request.getDeviceId()); + public ResponseEntity registerNonMember(@RequestBody NonMemberRequestDto nonMemberRequestDto) { + RequestUtil.checkNeedValue(nonMemberRequestDto.getDeviceId(), nonMemberRequestDto.getIsPC()); + Member nonMember = memberService.registerNon(nonMemberRequestDto.getDeviceId()); playlistService.createPlaylist("default", false, "OTHER", nonMember); - BaseResponseSuccessDto responseBody = new NonMemberCreateResponseDto(nonMember); + BaseResponseSuccessDto responseBody = new NonMemberResponseDto( + nonMember, + jwtTokenProvider.createToken(nonMember.getLoginId(), nonMember.getRole(), nonMemberRequestDto.getIsPC())); + + return ResponseEntity.ok(responseBody); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "비회원 로그인 성공", + content = @Content(schema = @Schema(implementation = NonMemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 아이디 존재 X\n" + + "2. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "로그인", description = "비회원 로그인") + @PostMapping("/login/non") + public ResponseEntity loginNonMember(@RequestBody NonMemberRequestDto nonMemberRequestDto) { + RequestUtil.checkNeedValue( + nonMemberRequestDto.getDeviceId(), + nonMemberRequestDto.getIsPC()); + + Member nonMember = memberService.findMemberByLoginId(nonMemberRequestDto.getDeviceId()); + + BaseResponseSuccessDto responseBody = new MemberResponseDto( + nonMember, + jwtTokenProvider.createToken(nonMember.getLoginId(), nonMember.getRole(), nonMemberRequestDto.getIsPC())); return ResponseEntity.ok(responseBody); } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "회원 생성 성공", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 중복된 아이디 존재\n" + + "2. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "가입", description = "회원 가입") + @PostMapping("/register") + public ResponseEntity registerMember(@RequestBody MemberRequestDto memberRequestDto) { + RequestUtil.checkNeedValue( + memberRequestDto.getLoginId(), + memberRequestDto.getPassword(), + memberRequestDto.getIsPC()); + Member member = memberService.register(memberRequestDto.getLoginId(), memberRequestDto.getPassword()); + playlistService.createPlaylist("default", false, "OTHER", member); + + BaseResponseSuccessDto responseBody = new MemberResponseDto( + member, + jwtTokenProvider.createToken(member.getLoginId(), member.getRole(), memberRequestDto.getIsPC())); + + return ResponseEntity.ok(responseBody); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "로그인 성공", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 아이디 존재 X\n" + + "2. 비밀번호 틀림 \n" + + "3. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "로그인", description = "회원 로그인") + @PostMapping("/login") + public ResponseEntity loginMember(@RequestBody MemberRequestDto memberRequestDto) { + RequestUtil.checkNeedValue( + memberRequestDto.getLoginId(), + memberRequestDto.getPassword(), + memberRequestDto.getIsPC()); + Member member = memberService.login(memberRequestDto.getLoginId(), memberRequestDto.getPassword()); + + BaseResponseSuccessDto responseBody = new MemberResponseDto( + member, + jwtTokenProvider.createToken(member.getLoginId(), member.getRole(), memberRequestDto.getIsPC())); + + return ResponseEntity.ok(responseBody); + } + + @DeleteMapping("/delete") + public ResponseEntity deleteMember(HttpServletRequest request) { + String token = jwtTokenProvider.resolveToken(request); + String loginId = jwtTokenProvider.getUserPk(token); + memberService.deleteUserByLoginId(loginId); + jwtTokenProvider.abandonToken(token); + + return ResponseEntity.ok(null); + } } diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index 035c836..13d1716 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -1,5 +1,6 @@ package com.example.youtubedb.controller; +import com.example.youtubedb.auth.JwtTokenProvider; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.BaseResponseSuccessDto; @@ -25,6 +26,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; @@ -37,11 +39,15 @@ public class PlayController { private final PlayService playService; private final PlaylistService playlistService; + private final JwtTokenProvider jwtTokenProvider; @Autowired - public PlayController(PlayService playService, PlaylistService playlistService) { + public PlayController(PlayService playService, + PlaylistService playlistService, + JwtTokenProvider jwtTokenProvider) { this.playService = playService; this.playlistService = playlistService; + this.jwtTokenProvider = jwtTokenProvider; } @ApiResponses(value = { @@ -51,7 +57,7 @@ public PlayController(PlayService playService, PlaylistService playlistService) @ApiResponse(responseCode = "400", description = "* 잘못된 요청\n " + "1. 필요값 X\n" + - "2. 플레이 리스트 존재 X" , + "2. 플레이 리스트 존재 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", description = "서버 에러", @@ -86,27 +92,28 @@ public ResponseEntity getPlays(@Parameter @PathVariable("playlistId") Long pl }) @PostMapping("/create") @Operation(summary = "생성", description = "영상 생성") - public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto request) { + public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto playCreateRequestDto, + HttpServletRequest request) { RequestUtil.checkNeedValue( -// request.getLoginId(), - request.getPlaylistId(), - request.getVideoId(), - request.getStart(), - request.getEnd(), - request.getThumbnail(), - request.getTitle(), - request.getChannelAvatar()); - - Playlist playlist = playlistService.getPlaylistById(request.getPlaylistId()); + playCreateRequestDto.getPlaylistId(), + playCreateRequestDto.getVideoId(), + playCreateRequestDto.getStart(), + playCreateRequestDto.getEnd(), + playCreateRequestDto.getThumbnail(), + playCreateRequestDto.getTitle(), + playCreateRequestDto.getChannelAvatar()); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); + + Playlist playlist = playlistService.getPlaylistById(playCreateRequestDto.getPlaylistId()); Play play = playService.addPlayToPlaylist( playlist, - "loginId?", - request.getVideoId(), - request.getStart(), - request.getEnd(), - request.getThumbnail(), - request.getTitle(), - request.getChannelAvatar()); + loginId, + playCreateRequestDto.getVideoId(), + playCreateRequestDto.getStart(), + playCreateRequestDto.getEnd(), + playCreateRequestDto.getThumbnail(), + playCreateRequestDto.getTitle(), + playCreateRequestDto.getChannelAvatar()); BaseResponseSuccessDto responseBody = new PlayCreateResponseDto(play); @@ -129,20 +136,22 @@ public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto request) { }) @PutMapping("/edit/time") @Operation(summary = "재생 시간 변경", description = "영상 재생시간 변경") - public ResponseEntity editPlayTime(@RequestBody PlayEditTimeRequestDto request) { + public ResponseEntity editPlayTime(@RequestBody PlayEditTimeRequestDto playEditTimeRequestDto, + HttpServletRequest request) { RequestUtil.checkNeedValue( - request.getId(), - request.getStart(), - request.getEnd()); + playEditTimeRequestDto.getId(), + playEditTimeRequestDto.getStart(), + playEditTimeRequestDto.getEnd()); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); - Play play = playService.getPlayById(request.getId()); + Play play = playService.getPlayById(playEditTimeRequestDto.getId()); playService.editTime( play, - "loginId?", - request.getStart(), - request.getEnd()); + loginId, + playEditTimeRequestDto.getStart(), + playEditTimeRequestDto.getEnd()); - BaseResponseSuccessDto responseBody = new PlayEditTimeResponseDto(request.getId()); + BaseResponseSuccessDto responseBody = new PlayEditTimeResponseDto(playEditTimeRequestDto.getId()); return ResponseEntity.ok(responseBody); } @@ -167,15 +176,17 @@ public ResponseEntity editPlayTime(@RequestBody PlayEditTimeRequestDto reques content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "순서 변경", description = "영상 재생순서 변경") - public ResponseEntity editPlaySequence(@RequestBody PlayEditSeqRequestDto request) { + public ResponseEntity editPlaySequence(@RequestBody PlayEditSeqRequestDto playEditSeqRequestDto, + HttpServletRequest request) { RequestUtil.checkNeedValue( - request.getPlaylistId(), - request.getSeqList()); + playEditSeqRequestDto.getPlaylistId(), + playEditSeqRequestDto.getSeqList()); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); - playService.editSeq("loginId?", request.getPlaylistId(), request.getSeqList()); - List> result = ResponseUtil.getEditPlaysResponse(request.getSeqList()); + playService.editSeq(loginId, playEditSeqRequestDto.getPlaylistId(), playEditSeqRequestDto.getSeqList()); + List> result = ResponseUtil.getEditPlaysResponse(playEditSeqRequestDto.getSeqList()); - BaseResponseSuccessDto responseBody = new PlayEditSeqResponseDto(request.getSeqList()); + BaseResponseSuccessDto responseBody = new PlayEditSeqResponseDto(playEditSeqRequestDto.getSeqList()); return ResponseEntity.ok(responseBody); } @@ -195,11 +206,13 @@ public ResponseEntity editPlaySequence(@RequestBody PlayEditSeqRequestDto req content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "삭제", description = "영상 삭제") - public ResponseEntity deletePlay(@Parameter @PathVariable("id") Long id) { + public ResponseEntity deletePlay(@Parameter @PathVariable("id") Long id, + HttpServletRequest request) { RequestUtil.checkNeedValue(id); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); Play play = playService.getPlayById(id); - playService.deletePlayById(play, "loginId?"); + playService.deletePlayById(play, loginId); playService.sortPlaysInPlaylist(play.getPlaylist()); BaseResponseSuccessDto responseBody = new PlayDeleteResponseDto(id); diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 871cae8..5474a30 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -1,13 +1,11 @@ package com.example.youtubedb.controller; +import com.example.youtubedb.auth.JwtTokenProvider; import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.BaseResponseSuccessDto; -import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; -import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; -import com.example.youtubedb.dto.play.request.PlayCreateRequestDto; import com.example.youtubedb.dto.playlist.request.PlaylistCreateRequestDto; import com.example.youtubedb.dto.playlist.request.PlaylistEditTitleRequestDto; import com.example.youtubedb.dto.playlist.response.PlaylistCreateResponseDto; @@ -17,7 +15,6 @@ import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; -import com.example.youtubedb.util.ResponseUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -29,6 +26,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import java.util.List; // Spring Security로 JWT 적용시 변경 필요 @@ -38,11 +36,15 @@ public class PlaylistController { private final PlaylistService playlistService; private final MemberService memberService; + private final JwtTokenProvider jwtTokenProvider; @Autowired - public PlaylistController(PlaylistService playlistService, MemberService memberService) { + public PlaylistController(PlaylistService playlistService, + MemberService memberService, + JwtTokenProvider jwtTokenProvider) { this.playlistService = playlistService; this.memberService = memberService; + this.jwtTokenProvider = jwtTokenProvider; } @ApiResponses(value = { @@ -59,8 +61,9 @@ public PlaylistController(PlaylistService playlistService, MemberService memberS content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "조회", description = "플레이 리스트들 조회") - @GetMapping("/{loginId}") - public ResponseEntity getPlaylist(@Parameter @PathVariable("loginId") String loginId) { + @GetMapping("/") + public ResponseEntity getPlaylist(HttpServletRequest request) { + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); Member member = memberService.findMemberByLoginId(loginId); List playlists = member.getPlaylists(); playlistService.addThumbnail(playlists); @@ -85,17 +88,19 @@ public ResponseEntity getPlaylist(@Parameter @PathVariable("loginId") String }) @PostMapping("/create") @Operation(summary = "생성", description = "플레이 리스트 생성") - public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto request) { // 여긴 임시로 loginId 필요 + public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto playlistCreateRequestDto, + HttpServletRequest request) { // 여긴 임시로 loginId 필요 RequestUtil.checkNeedValue( - request.getLoginId(), - request.getTitle(), - request.getIsPublic(), - request.getCategory()); - Member member = memberService.findMemberByLoginId(request.getLoginId()); + playlistCreateRequestDto.getTitle(), + playlistCreateRequestDto.getIsPublic(), + playlistCreateRequestDto.getCategory()); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); + + Member member = memberService.findMemberByLoginId(loginId); Playlist playlist = playlistService.createPlaylist( - request.getTitle(), - request.getIsPublic(), - request.getCategory(), + playlistCreateRequestDto.getTitle(), + playlistCreateRequestDto.getIsPublic(), + playlistCreateRequestDto.getCategory(), member); BaseResponseSuccessDto responseBody = new PlaylistCreateResponseDto(playlist); @@ -119,13 +124,16 @@ public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto re }) @PutMapping("/edit") @Operation(summary = "제목 수정", description = "플레이 리스트 제목 수정") - public ResponseEntity editPlaylist(@RequestBody PlaylistEditTitleRequestDto request) { + public ResponseEntity editPlaylist(@RequestBody PlaylistEditTitleRequestDto playlistEditTitleRequestDto, + HttpServletRequest request) { RequestUtil.checkNeedValue( - request.getId(), - request.getTitle()); - playlistService.editPlaylistTitle(request.getId(), request.getTitle(), "loginId"); + playlistEditTitleRequestDto.getId(), + playlistEditTitleRequestDto.getTitle()); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); - BaseResponseSuccessDto responseBody = new PlaylistEditTitleResponseDto(request.getId()); + playlistService.editPlaylistTitle(playlistEditTitleRequestDto.getId(), playlistEditTitleRequestDto.getTitle(), loginId); + + BaseResponseSuccessDto responseBody = new PlaylistEditTitleResponseDto(playlistEditTitleRequestDto.getId()); return ResponseEntity.ok(responseBody); } @@ -145,9 +153,12 @@ public ResponseEntity editPlaylist(@RequestBody PlaylistEditTitleRequestDto r }) @DeleteMapping("/delete/{id}") @Operation(summary = "삭제", description = "플레이 리스트 삭제") - public ResponseEntity deletePlaylist(@Parameter @PathVariable("id") Long id) { + public ResponseEntity deletePlaylist(@Parameter @PathVariable("id") Long id, + HttpServletRequest request) { RequestUtil.checkNeedValue(id); - playlistService.deletePlaylistById(id, "loginId?"); + String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); + + playlistService.deletePlaylistById(id, loginId); BaseResponseSuccessDto responseBody = new PlaylistDeleteResponseDto(id); diff --git a/src/main/java/com/example/youtubedb/controller/TestController.java b/src/main/java/com/example/youtubedb/controller/TestController.java new file mode 100644 index 0000000..4c27cf5 --- /dev/null +++ b/src/main/java/com/example/youtubedb/controller/TestController.java @@ -0,0 +1,20 @@ +package com.example.youtubedb.controller; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.member.request.NonMemberRequestDto; +import com.example.youtubedb.dto.member.response.NonMemberResponseDto; +import com.example.youtubedb.util.RequestUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @GetMapping("/admin/test") + public String getTest() { + return "ADMIN TEST"; + } +} diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/Member.java index 8c0f0fa..e3a2318 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/Member.java @@ -1,22 +1,30 @@ package com.example.youtubedb.domain; import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; @Entity @Getter -@NoArgsConstructor -public class Member extends BaseEntity { +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseEntity implements UserDetails { private String loginId; @JsonIgnore private String password; private boolean isMember; + private final String role = "USER"; + @JsonIgnore @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List playlists = new ArrayList<>(); @@ -27,4 +35,40 @@ public Member(String loginId, String password, boolean isMember) { this.password = password; this.isMember = isMember; } + + @JsonIgnore + @Override + public Collection getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + this.role)); // Authority라서 ROLE_" 추가 + } + + @JsonIgnore + @Override + public String getUsername() { + return null; + } + + @JsonIgnore + @Override + public boolean isAccountNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isAccountNonLocked() { + return true; + } + + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isEnabled() { + return true; + } } diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index f3f5f63..fd42994 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -1,10 +1,7 @@ package com.example.youtubedb.domain; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import javax.persistence.Entity; import javax.persistence.JoinColumn; @@ -12,7 +9,7 @@ @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Play extends BaseEntity { private String videoId; private long start; diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index e98f000..e51ddc1 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -2,10 +2,7 @@ import com.example.youtubedb.dto.Category; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import javax.persistence.*; import java.util.ArrayList; @@ -13,7 +10,7 @@ @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Playlist extends BaseEntity { @Setter private String title; diff --git a/src/main/java/com/example/youtubedb/dto/error/AccessDenyFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/AccessDenyFailResponseDto.java new file mode 100644 index 0000000..4108574 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/error/AccessDenyFailResponseDto.java @@ -0,0 +1,20 @@ +package com.example.youtubedb.dto.error; + +import com.example.youtubedb.dto.BaseResponseFailDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class AccessDenyFailResponseDto extends BaseResponseFailDto { + @Schema(description = "에러 상태 코드", defaultValue = "403") + private final int status; + @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") + private final String message; + + @Builder + public AccessDenyFailResponseDto(int status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/error/AuthenticationEntryPointFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/AuthenticationEntryPointFailResponseDto.java new file mode 100644 index 0000000..896dd02 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/error/AuthenticationEntryPointFailResponseDto.java @@ -0,0 +1,19 @@ +package com.example.youtubedb.dto.error; + +import com.example.youtubedb.dto.BaseResponseFailDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Getter +public class AuthenticationEntryPointFailResponseDto extends BaseResponseFailDto { + @Schema(description = "에러 상태 코드", defaultValue = "401") + private final int status; + @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") + private final String message; + + @Builder + public AuthenticationEntryPointFailResponseDto(int status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java index b5cb32f..5350d59 100644 --- a/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/error/BadRequestFailResponseDto.java @@ -2,17 +2,17 @@ import com.example.youtubedb.dto.BaseResponseFailDto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class BadRequestFailResponseDto extends BaseResponseFailDto { @Schema(description = "에러 상태 코드", defaultValue = "400") - private int status; + private final int status; @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") - private String message; + private final String message; @Builder public BadRequestFailResponseDto(int status, String message) { diff --git a/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java index 798d8e9..138d0e0 100644 --- a/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/error/NotAcceptableFailResponseDto.java @@ -2,17 +2,17 @@ import com.example.youtubedb.dto.BaseResponseFailDto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class NotAcceptableFailResponseDto extends BaseResponseFailDto { @Schema(description = "에러 상태 코드", defaultValue = "406") - private int status; + private final int status; @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") - private String message; + private final String message; @Builder public NotAcceptableFailResponseDto(int status, String message) { diff --git a/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java b/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java index d731c81..c86d122 100644 --- a/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/error/ServerErrorFailResponseDto.java @@ -2,17 +2,17 @@ import com.example.youtubedb.dto.BaseResponseFailDto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class ServerErrorFailResponseDto extends BaseResponseFailDto { @Schema(description = "에러 상태 코드", defaultValue = "500") - private int status; + private final int status; @Schema(description = "에러 메시지", defaultValue = "응답 실패 원인") - private String message; + private final String message; @Builder public ServerErrorFailResponseDto(int status, String message) { diff --git a/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java new file mode 100644 index 0000000..6acaea9 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java @@ -0,0 +1,24 @@ +package com.example.youtubedb.dto.member.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class MemberRequestDto { + @Schema(description = "로그인 ID", example = "user001") + private final String loginId; + @Schema(description = "로그인 PASSWORD", example = "password123") + private final String password; + @Schema(description = "PC여부", example = "false") + private final Boolean isPC; + + @Builder + public MemberRequestDto(String loginId, String password, Boolean isPC) { + this.loginId = loginId; + this.password = password; + this.isPC = isPC; + } +} + + diff --git a/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberRequestDto.java similarity index 52% rename from src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java rename to src/main/java/com/example/youtubedb/dto/member/request/NonMemberRequestDto.java index a163678..b9ec679 100644 --- a/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberRequestDto.java @@ -1,18 +1,21 @@ package com.example.youtubedb.dto.member.request; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor -public class NonMemberCreateRequestDto { +public class NonMemberRequestDto { @Schema(description = "장치 ID", example = "device001") - private String deviceId; + private final String deviceId; + @Schema(description = "PC여부", example = "false") + private final Boolean isPC; @Builder - public NonMemberCreateRequestDto(String deviceId) { + public NonMemberRequestDto(String deviceId, Boolean isPC) { this.deviceId = deviceId; + this.isPC = isPC; } } diff --git a/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java new file mode 100644 index 0000000..b36eb06 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java @@ -0,0 +1,19 @@ +package com.example.youtubedb.dto.member.response; + +import com.example.youtubedb.domain.Member; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class MemberResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 회원") + private final Member response; + @Schema(description = "JWT 토큰") + private final String token; + + public MemberResponseDto(Member response, String token) { + this.response = response; + this. token = token; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/NonMemberResponseDto.java similarity index 60% rename from src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java rename to src/main/java/com/example/youtubedb/dto/member/response/NonMemberResponseDto.java index 4126b71..425f6e8 100644 --- a/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/member/response/NonMemberResponseDto.java @@ -6,11 +6,14 @@ import lombok.Getter; @Getter -public class NonMemberCreateResponseDto extends BaseResponseSuccessDto { +public class NonMemberResponseDto extends BaseResponseSuccessDto { @Schema(description = "생성된 비회원") private final Member response; + @Schema(description = "JWT 토큰") + private final String token; - public NonMemberCreateResponseDto(Member response) { + public NonMemberResponseDto(Member response, String token) { this.response = response; + this. token = token; } } diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java index 96b9bb5..20431ff 100644 --- a/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java @@ -6,22 +6,21 @@ import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class PlayCreateRequestDto { @Schema(description = "플레이 리스트 아이디" , example = "1") - private Long playlistId; + private final Long playlistId; @Schema(description = "비디오 아이디" , example = "video123") - private String videoId; + private final String videoId; @Schema(description = "시작 시간" , example = "100") - private Long start; + private final Long start; @Schema(description = "끝 시간" , example = "1000") - private Long end; + private final Long end; @Schema(description = "썸네일 이미지 주소" , example = "img1") - private String thumbnail; + private final String thumbnail; @Schema(description = "제목" , example = "제목1") - private String title; + private final String title; @Schema(description = "아바타 이미지 주소" , example = "img2") - private String channelAvatar; + private final String channelAvatar; @Builder public PlayCreateRequestDto( diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java index 418dc17..fc79231 100644 --- a/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditSeqRequestDto.java @@ -10,12 +10,11 @@ import java.util.List; @Getter -@NoArgsConstructor public class PlayEditSeqRequestDto { @Schema(description = "로그인 아이디", example = "tester") - private Long playlistId; + private final Long playlistId; @Schema(description = "영상 순서 목록") - private List seqList; + private final List seqList; @Builder public PlayEditSeqRequestDto(Long playlistId, List seqList) { diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java index dd5ffcd..f86a53a 100644 --- a/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayEditTimeRequestDto.java @@ -6,14 +6,13 @@ import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class PlayEditTimeRequestDto { @Schema(description = "플레이 아이디", example = "1") - private Long id; + private final Long id; @Schema(description = "시작 시간", example = "100") - private Long start; + private final Long start; @Schema(description = "끝 시간", example = "1000") - private Long end; + private final Long end; @Builder public PlayEditTimeRequestDto(Long id, Long start, Long end) { diff --git a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java index aefe3f9..3d1eaf0 100644 --- a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistCreateRequestDto.java @@ -7,20 +7,16 @@ import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class PlaylistCreateRequestDto { - @Schema(description = "로그인 아이디", example = "tester") - private String loginId; @Schema(description = "제목", example = "영상 목록 1") - private String title; + private final String title; @Schema(description = "공개 여부", example = "true") - private Boolean isPublic; + private final Boolean isPublic; @Schema(description = "카테고리", example = "GAME") - private String category; + private final String category; @Builder - public PlaylistCreateRequestDto(String loginId, String title, Boolean isPublic, String category) { - this.loginId = loginId; + public PlaylistCreateRequestDto(String title, Boolean isPublic, String category) { this.title = title; this.isPublic = isPublic; this.category = category; diff --git a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java index 58a8e26..6e5ce26 100644 --- a/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/playlist/request/PlaylistEditTitleRequestDto.java @@ -6,12 +6,11 @@ import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor public class PlaylistEditTitleRequestDto { @Schema(description = "플레이 리스트 아이디", example = "1") - private Long id; + private final Long id; @Schema(description = "제목", example = "영상 목록 1") - private String title; + private final String title; @Builder public PlaylistEditTitleRequestDto(Long id, String title) { diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 28c9534..58ea9a8 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -1,11 +1,9 @@ package com.example.youtubedb.exception; import com.example.youtubedb.dto.BaseResponseFailDto; -import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.NotAcceptableFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; -import com.example.youtubedb.util.ResponseUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -13,7 +11,7 @@ @RestControllerAdvice public class ControllerExceptionHandler { - @ExceptionHandler({ + @ExceptionHandler(value = { NotExistRequestValueException.class, DuplicateMemberException.class, NotExistMemberException.class, @@ -21,7 +19,8 @@ public class ControllerExceptionHandler { StartAndEndTimeException.class, NotExistPlayException.class, DuplicateSeqException.class, - InvalidSeqException.class + InvalidSeqException.class, + DoNotMatchPasswordException.class }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/DoNotMatchPasswordException.java b/src/main/java/com/example/youtubedb/exception/DoNotMatchPasswordException.java new file mode 100644 index 0000000..1e36846 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/DoNotMatchPasswordException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class DoNotMatchPasswordException extends RuntimeException { + private static final String MESSAGE = "비밀번호가 일치하지 않습니다."; + + public DoNotMatchPasswordException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/repository/MemberRepository.java b/src/main/java/com/example/youtubedb/repository/MemberRepository.java index b79b0d7..4229867 100644 --- a/src/main/java/com/example/youtubedb/repository/MemberRepository.java +++ b/src/main/java/com/example/youtubedb/repository/MemberRepository.java @@ -8,4 +8,5 @@ public interface MemberRepository { Member save(Member member); Optional findByLoginId(String loginId); + void delete(Member member); } diff --git a/src/main/java/com/example/youtubedb/repository/PlayRepository.java b/src/main/java/com/example/youtubedb/repository/PlayRepository.java index db9c9d2..eef0c72 100644 --- a/src/main/java/com/example/youtubedb/repository/PlayRepository.java +++ b/src/main/java/com/example/youtubedb/repository/PlayRepository.java @@ -7,5 +7,5 @@ public interface PlayRepository { Play save(Play play); Optional findById(Long id); - void deleteById(Long id); + void delete(Play play); } diff --git a/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java b/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java index 70a6cef..00f53eb 100644 --- a/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java +++ b/src/main/java/com/example/youtubedb/repository/PlaylistRepository.java @@ -9,5 +9,5 @@ public interface PlaylistRepository { Playlist save(Playlist playlist); Optional findById(Long id); List findAll(); - void deleteById(Long id); + void delete(Playlist playlist); } diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 991590a..d0ce617 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -1,27 +1,30 @@ package com.example.youtubedb.service; import com.example.youtubedb.domain.Member; -import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.exception.DoNotMatchPasswordException; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; -import com.example.youtubedb.util.RequestUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; @Service @Transactional -public class MemberService { +public class MemberService implements UserDetailsService { private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; @Autowired - public MemberService(MemberRepository memberRepository) { + public MemberService(MemberRepository memberRepository, PasswordEncoder passwordEncoder) { this.memberRepository = memberRepository; + this.passwordEncoder = passwordEncoder; } @@ -36,8 +39,34 @@ public Member registerNon(String deviceId) { return memberRepository.save(nonMember); } - private void checkDuplicateMember(String deviceId) { - memberRepository.findByLoginId(deviceId).ifPresent(m -> { throw new DuplicateMemberException(); }); + public Member register(String loginId, String password) { // TODO : 비밀번호 생성 규칙 필요! + checkDuplicateMember(loginId); + Member member = Member.builder() + .isMember(true) + .loginId(loginId) + .password(passwordEncoder.encode(password)) + .build(); + + return memberRepository.save(member); + } + + private void checkDuplicateMember(String loginId) { + memberRepository.findByLoginId(loginId).ifPresent(m -> { + throw new DuplicateMemberException(); + }); + } + + public Member login(String loginId, String password) { + Member member = findMemberByLoginId(loginId); + checkPassword(password, member.getPassword()); + + return member; + } + + private void checkPassword(String password, String encodedPassword) { + if (!passwordEncoder.matches(password, encodedPassword)) { + throw new DoNotMatchPasswordException(); + } } public Member findMemberByLoginId(String loginId) { @@ -45,4 +74,14 @@ public Member findMemberByLoginId(String loginId) { member.orElseThrow(NotExistMemberException::new); return member.get(); } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return null; + } + + public void deleteUserByLoginId(String loginId) { + Member member = findMemberByLoginId(loginId); + memberRepository.delete(member); + } } diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index 9c5ade1..fad029b 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -36,7 +36,7 @@ public Play addPlayToPlaylist( String title, String channelAvatar) { checkTime(start, end); -// RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); + RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); int sequence = playlist.getPlays().size() + 1; @@ -62,7 +62,7 @@ private void checkTime(Long start, Long end) { } public List getPlaysInPlaylist(Playlist playlist, String loginId) { -// validateWatch(playlist, loginId); + validateWatch(playlist, loginId); return playlist.getPlays(); } @@ -74,8 +74,8 @@ private void validateWatch(Playlist playlist, String loginId) { } public void deletePlayById(Play play, String loginId) { -// RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); - playRepository.deleteById(play.getId()); + RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); + playRepository.delete(play); } public Play getPlayById(Long id) { @@ -83,7 +83,7 @@ public Play getPlayById(Long id) { } public void editTime(Play play, String loginId, long start, long end) { -// RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); + RequestUtil.checkOwn(play.getPlaylist().getMember().getLoginId(), loginId); checkTime(start, end); play.setTime(start, end); playRepository.save(play); @@ -94,7 +94,7 @@ public void editSeq(String loginId, Long playlistId, List seqList) { seqList.forEach(p -> { Play play = getPlayById(p.getId()); RequestUtil.checkOwn(playlistId, play.getPlaylist().getId()); -// RequestUtil.checkOwn(loginId, play.getPlaylist().getMember().getLoginId()); + RequestUtil.checkOwn(loginId, play.getPlaylist().getMember().getLoginId()); play.setSequence(p.getSequence()); }); } diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 31a12ee..8c30111 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -57,9 +57,9 @@ public Playlist editPlaylistTitle(Long id, String title, String loginId) { public void deletePlaylistById(Long id, String loginId) { Playlist playlist = getPlaylistById(id); -// RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); + RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); - playlistRepository.deleteById(id); + playlistRepository.delete(playlist); } public Playlist getPlaylistById(Long lId) { diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 6d04037..041a3d9 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,3 +1,7 @@ +jwt: + secret: testKey + time: 60000 # 1분 + spring: jpa: hibernate: @@ -8,4 +12,5 @@ spring: url: jdbc:h2:mem:testdb username: sa password: - driver-class-name: org.h2.Driver \ No newline at end of file + driver-class-name: org.h2.Driver + diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 1c23c9b..7a4d78f 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,4 +1,4 @@ --- insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); +-- insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123', 'ADMIN'); -- insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); -- insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); --- 테스트 데이터 (H2) \ No newline at end of file +-- 테스트 데이터 (H2) diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index f09ff63..d975a46 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -1,11 +1,13 @@ package com.example.youtubedb.service; import com.example.youtubedb.domain.Member; +import com.example.youtubedb.exception.DoNotMatchPasswordException; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @@ -17,6 +19,8 @@ class MemberServiceIntegrationTest { @Autowired MemberService memberService; + @Autowired + PasswordEncoder passwordEncoder; @Test void 비회원_등록() { @@ -35,7 +39,7 @@ class MemberServiceIntegrationTest { } @Test - void 등록시_중복() { + void 가입시_중복() { // given String deviceId = "device001"; @@ -75,4 +79,71 @@ class MemberServiceIntegrationTest { // then assertThat(e.getMessage()).isEqualTo(NotExistMemberException.getErrorMessage()); } + + @Test + void 회원_비회원_삭제() { + // given + String deviceId = "device001"; + Member nonMember = memberService.registerNon(deviceId); + + // when + memberService.deleteUserByLoginId(deviceId); + Exception e = assertThrows(NotExistMemberException.class, () -> memberService.findMemberByLoginId(deviceId)); + + // then + assertThat(e.getMessage()).isEqualTo(NotExistMemberException.getErrorMessage()); + } + + @Test + void 회원가입() { + // given + String loginId = "helloMan"; + String password = "hello123"; + + // when + Member member = memberService.register(loginId, password); + Member finded = memberService.findMemberByLoginId(loginId); + + // then + assertAll( + () -> assertThat(finded.getId()).isEqualTo(member.getId()), + () -> assertThat(finded.getLoginId()).isEqualTo(member.getLoginId()), + () -> assertThat(finded.getPassword()).isEqualTo(member.getPassword()), + () -> assertThat(finded.isMember()).isEqualTo(member.isMember()) + ); + } + + @Test + void 로그인() { + // given + String loginId = "helloMan"; + String password = "hello123"; + + // when + Member member = memberService.register(loginId, password); + Member loginMember = memberService.login(loginId, password); + + // then + assertAll( + () -> assertThat(loginMember.getId()).isEqualTo(member.getId()), + () -> assertThat(loginMember.getLoginId()).isEqualTo(member.getLoginId()), + () -> assertThat(loginMember.getPassword()).isEqualTo(member.getPassword()), + () -> assertThat(loginMember.isMember()).isEqualTo(member.isMember()) + ); + } + + @Test + void 로그인_비밀번호_불일치() { + // given + String loginId = "helloMan"; + String password = "hello123"; + String otherPassword = "bye123"; + Member member = memberService.register(loginId, password); + + // when + Exception e = assertThrows(DoNotMatchPasswordException.class, () -> memberService.login(loginId, otherPassword)); + + // then + assertThat(e.getMessage()).isEqualTo(DoNotMatchPasswordException.getErrorMessage()); + } } \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index 7e76858..2dd1cca 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -11,8 +11,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; From 3c0b975cf7ee40689688cb5f4b83c14ae7360905 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 8 Aug 2021 22:53:53 +0900 Subject: [PATCH 16/46] [FIX]SpringSecurity-jwt --- build.gradle | 7 +- .../example/youtubedb/config/RedisConfig.java | 39 +++++++ .../youtubedb/config/WebSecurityConfig.java | 1 + .../youtubedb/config/jwt/TokenProvider.java | 17 ++- .../youtubedb/controller/AuthController.java | 88 +++++++++++++-- .../controller/MemberController.java | 63 ----------- .../controller/PlaylistController.java | 6 +- .../example/youtubedb/domain/Playlist.java | 1 + .../com/example/youtubedb/domain/Token.java | 25 +++++ .../domain/{ => member}/Authority.java | 2 +- .../youtubedb/domain/{ => member}/Member.java | 9 +- .../member/request/MemberLoginRequestDto.java | 20 ++++ .../dto/member/request/MemberRequestDto.java | 29 ----- .../request/NonMemberCreateRequestDto.java | 4 +- .../request/RealMemberCreateRequestDto.java | 22 ++++ ...eDto.java => MemberCreateResponseDto.java} | 8 +- .../response/MemberLoginResponseDto.java | 16 +++ .../member/response/MemberResponseDto.java | 17 --- .../example/youtubedb/dto/token/TokenDto.java | 18 ---- .../youtubedb/dto/token/TokenRequestDto.java | 11 -- .../token/request/TokenReissueRequestDto.java | 20 ++++ .../dto/token/resposne/TokenResponseDto.java | 16 +++ .../repository/MemberRepository.java | 3 +- .../SpringDataJpaMemberRepository.java | 2 +- .../youtubedb/service/AuthService.java | 101 +++++++++++++----- .../service/CustomUserDetailsService.java | 2 +- .../youtubedb/service/MemberService.java | 6 +- .../youtubedb/service/PlayService.java | 2 - .../youtubedb/service/PlaylistService.java | 4 +- .../example/youtubedb/util/RedisUtils.java | 54 ++++++++++ src/main/resources/application-local.yaml | 4 +- src/main/resources/application.yaml | 9 +- src/main/resources/import.sql | 8 +- .../service/MemberServiceIntegrationTest.java | 2 +- .../service/PlayServiceIntegrationTest.java | 2 +- .../PlaylistServiceIntegrationTest.java | 4 +- 36 files changed, 418 insertions(+), 224 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/config/RedisConfig.java delete mode 100644 src/main/java/com/example/youtubedb/controller/MemberController.java create mode 100644 src/main/java/com/example/youtubedb/domain/Token.java rename src/main/java/com/example/youtubedb/domain/{ => member}/Authority.java (54%) rename src/main/java/com/example/youtubedb/domain/{ => member}/Member.java (77%) create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/MemberLoginRequestDto.java delete mode 100644 src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/RealMemberCreateRequestDto.java rename src/main/java/com/example/youtubedb/dto/member/response/{NonMemberCreateResponseDto.java => MemberCreateResponseDto.java} (54%) create mode 100644 src/main/java/com/example/youtubedb/dto/member/response/MemberLoginResponseDto.java delete mode 100644 src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java delete mode 100644 src/main/java/com/example/youtubedb/dto/token/TokenDto.java delete mode 100644 src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/token/request/TokenReissueRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/util/RedisUtils.java diff --git a/build.gradle b/build.gradle index dc7f1c1..9736300 100644 --- a/build.gradle +++ b/build.gradle @@ -32,8 +32,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' -// implementation "org.springframework.boot:spring-boot-starter-security" -// implementation 'org.springframework.security:spring-security-test' + //security implementation "org.springframework.boot:spring-boot-starter-security" @@ -44,6 +43,10 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' + //redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.modelmapper:modelmapper:2.3.6' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/config/RedisConfig.java b/src/main/java/com/example/youtubedb/config/RedisConfig.java new file mode 100644 index 0000000..99be06f --- /dev/null +++ b/src/main/java/com/example/youtubedb/config/RedisConfig.java @@ -0,0 +1,39 @@ +package com.example.youtubedb.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.modelmapper.convention.MatchingStrategies; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +@RequiredArgsConstructor +public class RedisConfig { + + private final ObjectMapper objectMapper; + private final RedisProperties redisProperties; + + @Bean + public ModelMapper modelMapper(){ + ModelMapper modelMapper = new ModelMapper(); + modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + return modelMapper; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(); + } + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } + +} diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index a7a5431..76612e7 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -43,6 +43,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() + .antMatchers("/api/playlist/**").hasRole("ADMIN") .anyRequest().authenticated() .and() diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 4e9c56c..0f85b71 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -1,10 +1,12 @@ package com.example.youtubedb.config.jwt; -import com.example.youtubedb.dto.token.TokenDto; +import com.example.youtubedb.domain.Token; +import com.example.youtubedb.util.RedisUtils; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -27,7 +29,8 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일 + private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7; // 7일 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; @@ -36,7 +39,7 @@ public TokenProvider(@Value("${jwt.secret}") String secretKey) { this.key = Keys.hmacShaKeyFor(keyBytes); } - public TokenDto generateTokenDto(Authentication authentication) { + public Token generateTokenDto(Authentication authentication, boolean isPc) { // 권한들 가져오기 String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) @@ -54,12 +57,16 @@ public TokenDto generateTokenDto(Authentication authentication) { .compact(); // Refresh Token 생성 + long expireTime = now + (isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP); + Date expireDate = new Date(expireTime); String refreshToken = Jwts.builder() - .setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME)) + .setExpiration(expireDate) .signWith(key, SignatureAlgorithm.HS512) .compact(); - return TokenDto.builder() +// RedisUtils.put(authentication.getName(), refreshToken, expireTime); + + return Token.builder() .grantType(BEARER_TYPE) .accessToken(accessToken) .accessTokenExpiresIn(accessTokenExpiresIn.getTime()) diff --git a/src/main/java/com/example/youtubedb/controller/AuthController.java b/src/main/java/com/example/youtubedb/controller/AuthController.java index f757cbe..ffbcc0e 100644 --- a/src/main/java/com/example/youtubedb/controller/AuthController.java +++ b/src/main/java/com/example/youtubedb/controller/AuthController.java @@ -1,10 +1,24 @@ package com.example.youtubedb.controller; -import com.example.youtubedb.dto.token.TokenDto; -import com.example.youtubedb.dto.member.request.MemberRequestDto; -import com.example.youtubedb.dto.member.response.MemberResponseDto; -import com.example.youtubedb.dto.token.TokenRequestDto; +import com.example.youtubedb.domain.member.Member; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.error.BadRequestFailResponseDto; +import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.member.request.MemberLoginRequestDto; +import com.example.youtubedb.dto.member.request.NonMemberCreateRequestDto; +import com.example.youtubedb.dto.member.response.MemberCreateResponseDto; +import com.example.youtubedb.domain.Token; +import com.example.youtubedb.dto.member.request.RealMemberCreateRequestDto; +import com.example.youtubedb.dto.token.request.TokenReissueRequestDto; +import com.example.youtubedb.dto.token.resposne.TokenResponseDto; import com.example.youtubedb.service.AuthService; +import com.example.youtubedb.service.PlaylistService; +import com.example.youtubedb.util.RequestUtil; +import io.swagger.v3.oas.annotations.Operation; +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.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,19 +28,71 @@ @RequiredArgsConstructor public class AuthController { private final AuthService authService; + private final PlaylistService playlistService; + + @PostMapping("/signup/real") + public ResponseEntity signupReal(@RequestBody RealMemberCreateRequestDto request) { + RequestUtil.checkNeedValue( + request.getLoginId(), + request.getPassword(), + request.getIsPc()); + + Member realMember = authService.registerReal(request.getLoginId(), request.getPassword(), request.getIsPc()); + playlistService.createPlaylist("default", false, "OTHER", realMember); + + BaseResponseSuccessDto responseBody = new MemberCreateResponseDto(realMember); + return ResponseEntity.ok(responseBody); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "비회원 생성 성공", + content = @Content(schema = @Schema(implementation = MemberCreateResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 중복된 아이디 존재\n" + + "2. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "가입", description = "비회원 가입") + @PostMapping("/signup/non") + public ResponseEntity signupNon(@RequestBody NonMemberCreateRequestDto request) { + RequestUtil.checkNeedValue( + request.getDeviceId(), + request.getIsPc()); + Member nonMember = authService.registerNon(request.getDeviceId(), request.getIsPc()); + playlistService.createPlaylist("default", false, "OTHER", nonMember); + + BaseResponseSuccessDto responseBody = new MemberCreateResponseDto(nonMember); + return ResponseEntity.ok(responseBody); - @PostMapping("/signup") - public ResponseEntity signup(@RequestBody MemberRequestDto memberRequestDto) { - return ResponseEntity.ok(authService.signup(memberRequestDto)); } @PostMapping("/login") - public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { - return ResponseEntity.ok(authService.login(memberRequestDto)); + public ResponseEntity login(@RequestBody MemberLoginRequestDto request) { + RequestUtil.checkNeedValue( + request.getLoginId(), + request.getPassword()); + Member loginMember = authService.findMemberByLoginId(request.getLoginId()); + String password = loginMember.isMember() ? request.getPassword() : loginMember.getLoginId(); + Token token = authService.login(request.getLoginId(), password, loginMember.isPc()); + + BaseResponseSuccessDto responseBody = new TokenResponseDto(token); + return ResponseEntity.ok(responseBody); } @PostMapping("/reissue") - public ResponseEntity reissue(@RequestBody TokenRequestDto tokenRequestDto) { - return ResponseEntity.ok(authService.reissue(tokenRequestDto)); + public ResponseEntity reissue(@RequestBody TokenReissueRequestDto request) { + RequestUtil.checkNeedValue( + request.getAccessToken(), + request.getRefreshToken()); +// request.getIsPc()); + Token token = authService.reissue(request.getAccessToken(), request.getRefreshToken(), request.getIsPc()); + + BaseResponseSuccessDto responseBody = new TokenResponseDto(token); + return ResponseEntity.ok(responseBody); } } diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java deleted file mode 100644 index 166efe5..0000000 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.example.youtubedb.controller; - -import com.example.youtubedb.domain.Member; -import com.example.youtubedb.dto.BaseResponseSuccessDto; -import com.example.youtubedb.dto.error.BadRequestFailResponseDto; -import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; -import com.example.youtubedb.dto.member.request.NonMemberCreateRequestDto; -import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; -import com.example.youtubedb.service.MemberService; -import com.example.youtubedb.service.PlaylistService; -import com.example.youtubedb.util.RequestUtil; -import io.swagger.v3.oas.annotations.Operation; -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.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -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; - -@Tag(name = "회원 관련 API") -@RestController -@RequestMapping("/api/member") -public class MemberController { - - private final MemberService memberService; - private final PlaylistService playlistService; - - @Autowired - public MemberController(MemberService memberService, PlaylistService playlistService) { - this.memberService = memberService; - this.playlistService = playlistService; - } - - @ApiResponses(value = { - @ApiResponse(responseCode = "200", - description = "비회원 생성 성공", - content = @Content(schema = @Schema(implementation = NonMemberCreateResponseDto.class))), - @ApiResponse(responseCode = "400", - description = "* 잘못된 요청\n" + - "1. 중복된 아이디 존재\n" + - "2. 필요값 X", - content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), - @ApiResponse(responseCode = "500", - description = "서버 에러", - content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) - }) - @Operation(summary = "가입", description = "비회원 가입") - @PostMapping("/register/non") - public ResponseEntity registerNonMember(@RequestBody NonMemberCreateRequestDto request) { - RequestUtil.checkNeedValue(request.getDeviceId()); - Member nonMember = memberService.registerNon(request.getDeviceId()); - playlistService.createPlaylist("default", false, "OTHER", nonMember); - - BaseResponseSuccessDto responseBody = new NonMemberCreateResponseDto(nonMember); - - return ResponseEntity.ok(responseBody); - } -} diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 871cae8..9dc44d4 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -1,13 +1,10 @@ package com.example.youtubedb.controller; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.BaseResponseSuccessDto; -import com.example.youtubedb.dto.ResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; -import com.example.youtubedb.dto.member.response.NonMemberCreateResponseDto; -import com.example.youtubedb.dto.play.request.PlayCreateRequestDto; import com.example.youtubedb.dto.playlist.request.PlaylistCreateRequestDto; import com.example.youtubedb.dto.playlist.request.PlaylistEditTitleRequestDto; import com.example.youtubedb.dto.playlist.response.PlaylistCreateResponseDto; @@ -17,7 +14,6 @@ import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; -import com.example.youtubedb.util.ResponseUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; diff --git a/src/main/java/com/example/youtubedb/domain/Playlist.java b/src/main/java/com/example/youtubedb/domain/Playlist.java index e98f000..124c669 100644 --- a/src/main/java/com/example/youtubedb/domain/Playlist.java +++ b/src/main/java/com/example/youtubedb/domain/Playlist.java @@ -1,5 +1,6 @@ package com.example.youtubedb.domain; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.dto.Category; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; diff --git a/src/main/java/com/example/youtubedb/domain/Token.java b/src/main/java/com/example/youtubedb/domain/Token.java new file mode 100644 index 0000000..3d09dae --- /dev/null +++ b/src/main/java/com/example/youtubedb/domain/Token.java @@ -0,0 +1,25 @@ +package com.example.youtubedb.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class Token{ + + private String grantType; + private String accessToken; + private String refreshToken; + private Long accessTokenExpiresIn; + + @Builder + + public Token(String grantType, String accessToken, String refreshToken, Long accessTokenExpiresIn) { + this.grantType = grantType; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.accessTokenExpiresIn = accessTokenExpiresIn; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/domain/Authority.java b/src/main/java/com/example/youtubedb/domain/member/Authority.java similarity index 54% rename from src/main/java/com/example/youtubedb/domain/Authority.java rename to src/main/java/com/example/youtubedb/domain/member/Authority.java index b9dc205..f6f88e3 100644 --- a/src/main/java/com/example/youtubedb/domain/Authority.java +++ b/src/main/java/com/example/youtubedb/domain/member/Authority.java @@ -1,4 +1,4 @@ -package com.example.youtubedb.domain; +package com.example.youtubedb.domain.member; public enum Authority { ROLE_USER, ROLE_ADMIN diff --git a/src/main/java/com/example/youtubedb/domain/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java similarity index 77% rename from src/main/java/com/example/youtubedb/domain/Member.java rename to src/main/java/com/example/youtubedb/domain/member/Member.java index 0433cc3..395b171 100644 --- a/src/main/java/com/example/youtubedb/domain/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -1,5 +1,7 @@ -package com.example.youtubedb.domain; +package com.example.youtubedb.domain.member; +import com.example.youtubedb.domain.BaseEntity; +import com.example.youtubedb.domain.Playlist; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; @@ -21,18 +23,19 @@ public class Member extends BaseEntity { @Enumerated private Authority authority; + private boolean isPc; @JsonIgnore @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List playlists = new ArrayList<>(); @Builder - public Member(String loginId, String password, boolean isMember, Authority authority){ + public Member(String loginId, String password, boolean isMember, Authority authority, boolean isPc){ this.loginId = loginId; this.password = password; this.isMember = isMember; this.authority = authority; - + this.isPc = isPc; } } \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/member/request/MemberLoginRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/MemberLoginRequestDto.java new file mode 100644 index 0000000..1db0907 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/MemberLoginRequestDto.java @@ -0,0 +1,20 @@ +package com.example.youtubedb.dto.member.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class MemberLoginRequestDto { + @Schema(description = "장치 ID", example = "device001") + private String loginId; + private String password; + + @Builder + public MemberLoginRequestDto(String loginId, String password) { + this.loginId = loginId; + this.password = password; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java deleted file mode 100644 index 0c9c56d..0000000 --- a/src/main/java/com/example/youtubedb/dto/member/request/MemberRequestDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.youtubedb.dto.member.request; - -import com.example.youtubedb.domain.Authority; -import com.example.youtubedb.domain.Member; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.crypto.password.PasswordEncoder; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class MemberRequestDto { - private String loginId; - private String password; - - public Member toMember(PasswordEncoder passwordEncoder) { - return Member.builder() - .loginId(loginId) - .password(passwordEncoder.encode(password)) - .authority(Authority.ROLE_USER) - .build(); - } - - public UsernamePasswordAuthenticationToken toAuthentication() { - return new UsernamePasswordAuthenticationToken(loginId, password); - } -} diff --git a/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java index a163678..dacbe91 100644 --- a/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/member/request/NonMemberCreateRequestDto.java @@ -10,9 +10,11 @@ public class NonMemberCreateRequestDto { @Schema(description = "장치 ID", example = "device001") private String deviceId; + private Boolean isPc; @Builder - public NonMemberCreateRequestDto(String deviceId) { + public NonMemberCreateRequestDto(String deviceId, boolean isPc) { this.deviceId = deviceId; + this.isPc = isPc; } } diff --git a/src/main/java/com/example/youtubedb/dto/member/request/RealMemberCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/RealMemberCreateRequestDto.java new file mode 100644 index 0000000..fde4b65 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/RealMemberCreateRequestDto.java @@ -0,0 +1,22 @@ +package com.example.youtubedb.dto.member.request; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + +@Getter +@NoArgsConstructor +public class RealMemberCreateRequestDto { + + private String loginId; + private String password; + private Boolean isPc; + + @Builder + public RealMemberCreateRequestDto(String loginId, String password, Boolean isPc){ + this.loginId = loginId; + this.password = password; + this.isPc = isPc; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberCreateResponseDto.java similarity index 54% rename from src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java rename to src/main/java/com/example/youtubedb/dto/member/response/MemberCreateResponseDto.java index 4126b71..432e17c 100644 --- a/src/main/java/com/example/youtubedb/dto/member/response/NonMemberCreateResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/member/response/MemberCreateResponseDto.java @@ -1,16 +1,16 @@ package com.example.youtubedb.dto.member.response; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.dto.BaseResponseSuccessDto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter -public class NonMemberCreateResponseDto extends BaseResponseSuccessDto { - @Schema(description = "생성된 비회원") +public class MemberCreateResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 회원") private final Member response; - public NonMemberCreateResponseDto(Member response) { + public MemberCreateResponseDto(Member response) { this.response = response; } } diff --git a/src/main/java/com/example/youtubedb/dto/member/response/MemberLoginResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberLoginResponseDto.java new file mode 100644 index 0000000..28f81d3 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/response/MemberLoginResponseDto.java @@ -0,0 +1,16 @@ +package com.example.youtubedb.dto.member.response; + +import com.example.youtubedb.domain.member.Member; +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class MemberLoginResponseDto extends BaseResponseSuccessDto { + @Schema(description = "생성된 회원") + private final Member response; + + public MemberLoginResponseDto(Member response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java deleted file mode 100644 index 76cb402..0000000 --- a/src/main/java/com/example/youtubedb/dto/member/response/MemberResponseDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.youtubedb.dto.member.response; - -import com.example.youtubedb.domain.Member; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class MemberResponseDto { - private String loginId; - - public static MemberResponseDto of(Member member) { - return new MemberResponseDto(member.getLoginId()); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/TokenDto.java b/src/main/java/com/example/youtubedb/dto/token/TokenDto.java deleted file mode 100644 index 1fb54e9..0000000 --- a/src/main/java/com/example/youtubedb/dto/token/TokenDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.youtubedb.dto.token; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class TokenDto { - - private String grantType; - private String accessToken; - private String refreshToken; - private Long accessTokenExpiresIn; -} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java b/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java deleted file mode 100644 index 71a2f8a..0000000 --- a/src/main/java/com/example/youtubedb/dto/token/TokenRequestDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.youtubedb.dto.token; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class TokenRequestDto { - private String accessToken; - private String refreshToken; -} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/request/TokenReissueRequestDto.java b/src/main/java/com/example/youtubedb/dto/token/request/TokenReissueRequestDto.java new file mode 100644 index 0000000..2d1e3c8 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/token/request/TokenReissueRequestDto.java @@ -0,0 +1,20 @@ +package com.example.youtubedb.dto.token.request; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TokenReissueRequestDto { + private String accessToken; + private String refreshToken; + private Boolean isPc; + + @Builder + public TokenReissueRequestDto(String accessToken, String refreshToken, Boolean isPc) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.isPc = isPc; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java b/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java new file mode 100644 index 0000000..1006692 --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java @@ -0,0 +1,16 @@ +package com.example.youtubedb.dto.token.resposne; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.domain.Token; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class TokenResponseDto extends BaseResponseSuccessDto { + @Schema(description = "토큰") + private final Token response; + + public TokenResponseDto(Token response) { + this.response = response; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/repository/MemberRepository.java b/src/main/java/com/example/youtubedb/repository/MemberRepository.java index d9cccbb..54f4afe 100644 --- a/src/main/java/com/example/youtubedb/repository/MemberRepository.java +++ b/src/main/java/com/example/youtubedb/repository/MemberRepository.java @@ -1,8 +1,7 @@ package com.example.youtubedb.repository; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; -import java.util.List; import java.util.Optional; public interface MemberRepository { diff --git a/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java b/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java index 2df4529..25baa88 100644 --- a/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java +++ b/src/main/java/com/example/youtubedb/repository/SpringDataJpaMemberRepository.java @@ -1,6 +1,6 @@ package com.example.youtubedb.repository; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface SpringDataJpaMemberRepository extends JpaRepository, MemberRepository { diff --git a/src/main/java/com/example/youtubedb/service/AuthService.java b/src/main/java/com/example/youtubedb/service/AuthService.java index c5c8364..2217859 100644 --- a/src/main/java/com/example/youtubedb/service/AuthService.java +++ b/src/main/java/com/example/youtubedb/service/AuthService.java @@ -1,15 +1,17 @@ package com.example.youtubedb.service; import com.example.youtubedb.config.jwt.TokenProvider; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Authority; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.domain.RefreshToken; -import com.example.youtubedb.dto.member.request.MemberRequestDto; -import com.example.youtubedb.dto.member.response.MemberResponseDto; -import com.example.youtubedb.dto.token.TokenDto; -import com.example.youtubedb.dto.token.TokenRequestDto; +import com.example.youtubedb.domain.Token; +import com.example.youtubedb.exception.DuplicateMemberException; +import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; import com.example.youtubedb.repository.RefreshTokenRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; @@ -17,8 +19,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + @Service @RequiredArgsConstructor +@Transactional public class AuthService { private final AuthenticationManagerBuilder authenticationManagerBuilder; @@ -27,68 +32,112 @@ public class AuthService { private final TokenProvider tokenProvider; private final RefreshTokenRepository refreshTokenRepository; - @Transactional - public MemberResponseDto signup(MemberRequestDto memberRequestDto) { - if (memberRepository.existsByLoginId(memberRequestDto.getLoginId())) { - throw new RuntimeException("이미 가입되어 있는 유저입니다"); - } + private final RedisTemplate redisTemplate; + + + public Member registerNon(String deviceId, Boolean isPc) { + checkDuplicateMember(deviceId); + + Member nonMember = Member.builder() + .isMember(false) + .loginId(deviceId) + .authority(Authority.ROLE_USER) + .password(passwordEncoder.encode(deviceId)) + .isPc(isPc) + .build(); + + return memberRepository.save(nonMember); - Member member = memberRequestDto.toMember(passwordEncoder); - return MemberResponseDto.of(memberRepository.save(member)); } - @Transactional - public TokenDto login(MemberRequestDto memberRequestDto) { + public Member registerReal(String loginId, String password, Boolean isPc) { + checkDuplicateMember(loginId); + + Member realMember = Member.builder() + .isMember(true) + .loginId(loginId) + .authority(Authority.ROLE_USER) + .password(passwordEncoder.encode(password)) + .isPc(isPc) + .build(); + + return memberRepository.save(realMember); + } + + public Token login(String loginID, String password, boolean isPc) { // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 - UsernamePasswordAuthenticationToken authenticationToken = memberRequestDto.toAuthentication(); + UsernamePasswordAuthenticationToken authenticationToken = toAuthentication(loginID, password); // 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분 // authenticate 메서드가 실행이 될 때 CustomUserDetailsService 에서 만들었던 loadUserByUsername 메서드가 실행됨 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); // 3. 인증 정보를 기반으로 JWT 토큰 생성 - TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); + Token token = tokenProvider.generateTokenDto(authentication, isPc); // 4. RefreshToken 저장 RefreshToken refreshToken = RefreshToken.builder() .key(authentication.getName()) - .value(tokenDto.getRefreshToken()) + .value(token.getRefreshToken()) .build(); refreshTokenRepository.save(refreshToken); + //redis 활용 중 - 현재 저장만, expire 못함 +// RedisUtils.put(authentication.getName(), token.getRefreshToken(), token.re); + // 5. 토큰 발급 - return tokenDto; + return token; } @Transactional - public TokenDto reissue(TokenRequestDto tokenRequestDto) { + public Token reissue(String accessToken, String refreshToken, boolean isPc +// TokenReissueRequestDto tokenRequestDto + ) { // 1. Refresh Token 검증 - if (!tokenProvider.validateToken(tokenRequestDto.getRefreshToken())) { + if (!tokenProvider.validateToken(refreshToken)) { throw new RuntimeException("Refresh Token 이 유효하지 않습니다."); } - // 2. Access Token 에서 Member ID 가져오기 - Authentication authentication = tokenProvider.getAuthentication(tokenRequestDto.getAccessToken()); + // 2. Access Token 에서 Member ID(pk) 가져오기 + Authentication authentication = tokenProvider.getAuthentication(accessToken); // 3. 저장소에서 Member ID 를 기반으로 Refresh Token 값 가져옴 - RefreshToken refreshToken = refreshTokenRepository.findByKey(authentication.getName()) + RefreshToken findByIdRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) .orElseThrow(() -> new RuntimeException("로그아웃 된 사용자입니다.")); // 4. Refresh Token 일치하는지 검사 - if (!refreshToken.getValue().equals(tokenRequestDto.getRefreshToken())) { + if (!findByIdRefreshToken.getValue().equals(refreshToken)) { throw new RuntimeException("토큰의 유저 정보가 일치하지 않습니다."); } // 5. 새로운 토큰 생성 - TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); + Token tokenDto = tokenProvider.generateTokenDto(authentication, isPc); // 6. 저장소 정보 업데이트 - RefreshToken newRefreshToken = refreshToken.updateValue(tokenDto.getRefreshToken()); + RefreshToken newRefreshToken = findByIdRefreshToken.updateValue(tokenDto.getRefreshToken()); refreshTokenRepository.save(newRefreshToken); // 토큰 발급 return tokenDto; } + public Member findMemberByLoginId(String loginId) { + Optional member = memberRepository.findByLoginId(loginId); + member.orElseThrow(NotExistMemberException::new); + return member.get(); + } + + private void checkDuplicateMember(String loginId) { + memberRepository.findByLoginId(loginId).ifPresent(m -> { + throw new DuplicateMemberException(); + }); + } + + private UsernamePasswordAuthenticationToken toAuthentication(String loginId, String password) { + return new UsernamePasswordAuthenticationToken(loginId, password); + } + + + } diff --git a/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java index 2372edc..6596a96 100644 --- a/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java +++ b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java @@ -1,6 +1,6 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 1694775..e54f19a 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -1,17 +1,13 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; -import com.example.youtubedb.domain.Playlist; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import com.example.youtubedb.repository.MemberRepository; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; @Service diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index 9c5ade1..de65819 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -1,6 +1,5 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.play.PlaySeqDto; @@ -11,7 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 31a12ee..1e8a67d 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -1,14 +1,12 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.Category; -import com.example.youtubedb.exception.InvalidAccessException; import com.example.youtubedb.exception.NotExistPlaylistException; import com.example.youtubedb.exception.NotExistRequestValueException; import com.example.youtubedb.repository.PlaylistRepository; -import com.example.youtubedb.util.RequestUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/example/youtubedb/util/RedisUtils.java b/src/main/java/com/example/youtubedb/util/RedisUtils.java new file mode 100644 index 0000000..9076be4 --- /dev/null +++ b/src/main/java/com/example/youtubedb/util/RedisUtils.java @@ -0,0 +1,54 @@ +package com.example.youtubedb.util; + +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.concurrent.TimeUnit; + + +@Component +@RequiredArgsConstructor +public class RedisUtils { + private final RedisTemplate redisTemplate; + private final ModelMapper modelMapper; + + public void put(String key, Object value, Long expirationTime){ + if(expirationTime != null){ + redisTemplate.opsForValue().set(key, value, expirationTime, TimeUnit.SECONDS); + }else{ + redisTemplate.opsForValue().set(key, value); + } + } + + public void delete(String key){ + redisTemplate.delete(key); + } + + public T get(String key, Class clazz){ + Object o = redisTemplate.opsForValue().get(key); + if(o != null) { + if(o instanceof LinkedHashMap){ + return modelMapper.map(o, clazz); + }else{ + return clazz.cast(o); + } + } + return null; + } + + public boolean isExists(String key){ + return redisTemplate.hasKey(key); + } + + public void setExpireTime(String key, long expirationTime){ + redisTemplate.expire(key, expirationTime, TimeUnit.SECONDS); + } + + public long getExpireTime(String key){ + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } +} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 6d04037..e311534 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -4,8 +4,8 @@ spring: ddl-auto: create datasource: - # url: jdbc:h2:tcp://localhost/~/spring - url: jdbc:h2:mem:testdb + url: jdbc:h2:tcp://localhost/~/spring +# url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e4eec1a..1d0375d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,6 +23,12 @@ spring: show_sql: true format_sql: true + redis: + lettuce: + pool: + max-active: 10 + + logging: level: org.hibernate.SQL: debug @@ -35,14 +41,11 @@ management: web: exposure: include: "*" - # include: health, metrics base-path: "/admin/monitoring" endpoint: health: - # enabled: true - # show-details: always group: custom: include: diskSpace, ping diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 1c23c9b..c3ae760 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,4 +1,4 @@ --- insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); --- insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); --- insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); --- 테스트 데이터 (H2) \ No newline at end of file +insert into MEMBER values(1, NOW(), NOW(), false, 'helloMan', 'hello123'); +insert into PLAYLIST values(1, NOW(), NOW(),'GAME', true, 0, 'myList', 1); +insert into PLAY values(1, NOW(), NOW(), 'avatar', 1000, 1, 100, 'img', 'title', 'VIDEOID', 1); +테스트 데이터 (H2) \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index f09ff63..da816d4 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -1,6 +1,6 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index 7ec302b..a58997c 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -1,6 +1,6 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.play.PlaySeqDto; diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index 7e76858..cf39f9b 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -1,6 +1,6 @@ package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.domain.Play; import com.example.youtubedb.domain.Playlist; import com.example.youtubedb.dto.Category; @@ -11,8 +11,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; From 93e14711fc583d4580a1f93f582dae0558d17adb Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 8 Aug 2021 22:55:46 +0900 Subject: [PATCH 17/46] [FIX]EXPIREDATE --- .../java/com/example/youtubedb/config/jwt/TokenProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 0f85b71..83f0344 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -29,8 +29,8 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7; // 7일 - private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7; // 7일 + private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; From c03df423c8994b2bea5e240cf1ff55b456b8fa9d Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 8 Aug 2021 23:36:43 +0900 Subject: [PATCH 18/46] [FIX]redis --- .../example/youtubedb/config/RedisConfig.java | 4 +++ .../youtubedb/config/jwt/TokenProvider.java | 9 ++++--- .../youtubedb/controller/AuthController.java | 1 + .../youtubedb/service/AuthService.java | 1 - .../example/youtubedb/util/RedisUtils.java | 25 ++++++++----------- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/RedisConfig.java b/src/main/java/com/example/youtubedb/config/RedisConfig.java index 99be06f..e363cd2 100644 --- a/src/main/java/com/example/youtubedb/config/RedisConfig.java +++ b/src/main/java/com/example/youtubedb/config/RedisConfig.java @@ -10,6 +10,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @RequiredArgsConstructor @@ -33,6 +34,9 @@ public RedisConnectionFactory redisConnectionFactory() { public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + return redisTemplate; } diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 83f0344..eb37eba 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -6,7 +6,6 @@ import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -33,8 +32,10 @@ public class TokenProvider { private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; + private final RedisUtils redisUtils; - public TokenProvider(@Value("${jwt.secret}") String secretKey) { + public TokenProvider(@Value("${jwt.secret}") String secretKey, RedisUtils redisUtils) { + this.redisUtils = redisUtils; byte[] keyBytes = Decoders.BASE64.decode(secretKey); this.key = Keys.hmacShaKeyFor(keyBytes); } @@ -64,7 +65,9 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { .signWith(key, SignatureAlgorithm.HS512) .compact(); -// RedisUtils.put(authentication.getName(), refreshToken, expireTime); + +// redisUtils.put("1", "2", expireTime); + redisUtils.put(authentication.getName(), refreshToken, expireTime); return Token.builder() .grantType(BEARER_TYPE) diff --git a/src/main/java/com/example/youtubedb/controller/AuthController.java b/src/main/java/com/example/youtubedb/controller/AuthController.java index ffbcc0e..09039c4 100644 --- a/src/main/java/com/example/youtubedb/controller/AuthController.java +++ b/src/main/java/com/example/youtubedb/controller/AuthController.java @@ -73,6 +73,7 @@ public ResponseEntity signupNon(@RequestBody NonMemberCreateRequestDto reques @PostMapping("/login") public ResponseEntity login(@RequestBody MemberLoginRequestDto request) { + RequestUtil.checkNeedValue( request.getLoginId(), request.getPassword()); diff --git a/src/main/java/com/example/youtubedb/service/AuthService.java b/src/main/java/com/example/youtubedb/service/AuthService.java index 2217859..c9a1ecf 100644 --- a/src/main/java/com/example/youtubedb/service/AuthService.java +++ b/src/main/java/com/example/youtubedb/service/AuthService.java @@ -81,7 +81,6 @@ public Token login(String loginID, String password, boolean isPc) { .value(token.getRefreshToken()) .build(); - refreshTokenRepository.save(refreshToken); //redis 활용 중 - 현재 저장만, expire 못함 // RedisUtils.put(authentication.getName(), token.getRefreshToken(), token.re); diff --git a/src/main/java/com/example/youtubedb/util/RedisUtils.java b/src/main/java/com/example/youtubedb/util/RedisUtils.java index 9076be4..e5e73cc 100644 --- a/src/main/java/com/example/youtubedb/util/RedisUtils.java +++ b/src/main/java/com/example/youtubedb/util/RedisUtils.java @@ -9,46 +9,41 @@ import java.util.LinkedHashMap; import java.util.concurrent.TimeUnit; - @Component @RequiredArgsConstructor public class RedisUtils { private final RedisTemplate redisTemplate; private final ModelMapper modelMapper; - public void put(String key, Object value, Long expirationTime){ - if(expirationTime != null){ - redisTemplate.opsForValue().set(key, value, expirationTime, TimeUnit.SECONDS); - }else{ - redisTemplate.opsForValue().set(key, value); - } + public void put(String key, Object value, long expirationTime) { + redisTemplate.opsForValue().set(key, value, expirationTime, TimeUnit.SECONDS); } - public void delete(String key){ + public void delete(String key) { redisTemplate.delete(key); } - public T get(String key, Class clazz){ + public T get(String key, Class clazz) { Object o = redisTemplate.opsForValue().get(key); - if(o != null) { - if(o instanceof LinkedHashMap){ + if (o != null) { + if (o instanceof LinkedHashMap) { return modelMapper.map(o, clazz); - }else{ + } else { return clazz.cast(o); } } return null; } - public boolean isExists(String key){ + public boolean isExists(String key) { return redisTemplate.hasKey(key); } - public void setExpireTime(String key, long expirationTime){ + public void setExpireTime(String key, long expirationTime) { redisTemplate.expire(key, expirationTime, TimeUnit.SECONDS); } - public long getExpireTime(String key){ + public long getExpireTime(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } } \ No newline at end of file From 2759c8e79fc8f6899369ea89f1e610be1bc73df5 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 8 Aug 2021 23:42:18 +0900 Subject: [PATCH 19/46] [DELETE]memberService --- .../controller/PlaylistController.java | 12 +-- .../youtubedb/service/MemberService.java | 39 ---------- .../service/MemberServiceIntegrationTest.java | 78 ------------------- .../service/PlayServiceIntegrationTest.java | 22 +++--- .../PlaylistServiceIntegrationTest.java | 16 ++-- 5 files changed, 26 insertions(+), 141 deletions(-) delete mode 100644 src/main/java/com/example/youtubedb/service/MemberService.java delete mode 100644 src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 9dc44d4..1050777 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -11,7 +11,7 @@ import com.example.youtubedb.dto.playlist.response.PlaylistDeleteResponseDto; import com.example.youtubedb.dto.playlist.response.PlaylistEditTitleResponseDto; import com.example.youtubedb.dto.playlist.response.PlaylistGetResponseDto; -import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.service.AuthService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import io.swagger.v3.oas.annotations.Operation; @@ -33,12 +33,12 @@ @RequestMapping("/api/playlist") public class PlaylistController { private final PlaylistService playlistService; - private final MemberService memberService; + private final AuthService authService; @Autowired - public PlaylistController(PlaylistService playlistService, MemberService memberService) { + public PlaylistController(PlaylistService playlistService, AuthService authService) { this.playlistService = playlistService; - this.memberService = memberService; + this.authService = authService; } @ApiResponses(value = { @@ -57,7 +57,7 @@ public PlaylistController(PlaylistService playlistService, MemberService memberS @Operation(summary = "조회", description = "플레이 리스트들 조회") @GetMapping("/{loginId}") public ResponseEntity getPlaylist(@Parameter @PathVariable("loginId") String loginId) { - Member member = memberService.findMemberByLoginId(loginId); + Member member = authService.findMemberByLoginId(loginId); List playlists = member.getPlaylists(); playlistService.addThumbnail(playlists); @@ -87,7 +87,7 @@ public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto re request.getTitle(), request.getIsPublic(), request.getCategory()); - Member member = memberService.findMemberByLoginId(request.getLoginId()); + Member member = authService.findMemberByLoginId(request.getLoginId()); Playlist playlist = playlistService.createPlaylist( request.getTitle(), request.getIsPublic(), diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java deleted file mode 100644 index e54f19a..0000000 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.youtubedb.service; - -import com.example.youtubedb.domain.member.Member; -import com.example.youtubedb.exception.DuplicateMemberException; -import com.example.youtubedb.exception.NotExistMemberException; -import com.example.youtubedb.repository.MemberRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Optional; - -@Service -@RequiredArgsConstructor -@Transactional -public class MemberService { - private final MemberRepository memberRepository; - - public Member registerNon(String deviceId) { - checkDuplicateMember(deviceId); - Member nonMember = Member.builder() - .isMember(false) - .loginId(deviceId) - .password(null) - .build(); - - return memberRepository.save(nonMember); - } - - private void checkDuplicateMember(String deviceId) { - memberRepository.findByLoginId(deviceId).ifPresent(m -> { throw new DuplicateMemberException(); }); - } - - public Member findMemberByLoginId(String loginId) { - Optional member = memberRepository.findByLoginId(loginId); - member.orElseThrow(NotExistMemberException::new); - return member.get(); - } -} diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java deleted file mode 100644 index da816d4..0000000 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.youtubedb.service; - -import com.example.youtubedb.domain.member.Member; -import com.example.youtubedb.exception.DuplicateMemberException; -import com.example.youtubedb.exception.NotExistMemberException; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@SpringBootTest -@Transactional -class MemberServiceIntegrationTest { - @Autowired - MemberService memberService; - - @Test - void 비회원_등록() { - // given - String deviceId = "device001"; - - // when - Member nonMember = memberService.registerNon(deviceId); - - // then - assertAll( - () -> assertThat(nonMember.isMember()).isEqualTo(false), - () -> assertThat(nonMember.getLoginId()).isEqualTo(deviceId), - () -> assertThat(nonMember.getPassword()).isEqualTo(null) - ); - } - - @Test - void 등록시_중복() { - // given - String deviceId = "device001"; - - // when - memberService.registerNon(deviceId); - Exception e = assertThrows(DuplicateMemberException.class, () -> memberService.registerNon(deviceId)); - - // then - assertThat(e.getMessage()).isEqualTo(DuplicateMemberException.getErrorMessage()); - } - - @Test - void loginId_조회() { - // given - String deviceId = "device001"; - Member member = memberService.registerNon(deviceId); - - // when - Member result = memberService.findMemberByLoginId(member.getLoginId()); - - // then - assertAll( - () -> assertThat(result.isMember()).isEqualTo(false), - () -> assertThat(result.getLoginId()).isEqualTo(deviceId), - () -> assertThat(result.getPassword()).isEqualTo(null) - ); - } - - @Test - void loginId_조회_존재X() { - // given - String deviceId = "device001"; - - // when - Exception e = assertThrows(NotExistMemberException.class, () -> memberService.findMemberByLoginId(deviceId)); - - // then - assertThat(e.getMessage()).isEqualTo(NotExistMemberException.getErrorMessage()); - } -} \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index a58997c..413c5c2 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -24,7 +24,7 @@ class PlayServiceIntegrationTest { @Autowired private PlayService playService; @Autowired - private MemberService memberService; + private AuthService authService; @Autowired private PlaylistService playlistService; @@ -34,10 +34,12 @@ class PlayServiceIntegrationTest { private String thumbnail; private String title; private String channelAvatar; + private boolean isPc; @BeforeEach void setup() { this.videoId = "video001"; + this.isPc =true; this.start = 100; this.end = 1000; this.thumbnail = "썸네일"; @@ -48,7 +50,7 @@ void setup() { @Test void 플레이_추가() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); // when @@ -78,7 +80,7 @@ void setup() { @Test void 플레이_추가_시간예외() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); // when @@ -101,7 +103,7 @@ void setup() { @Test void 영상목록_조회() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); playService.addPlayToPlaylist( playlist, @@ -149,7 +151,7 @@ void setup() { @Test void 영상_시간_수정() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play = playService.addPlayToPlaylist( playlist, @@ -175,7 +177,7 @@ void setup() { @Test void 영상_순서_수정() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, @@ -223,7 +225,7 @@ void setup() { @Test void 영상_순서_수정_순서이상() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, @@ -267,7 +269,7 @@ void setup() { @Test void 영상_순서_수정_중복() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, @@ -311,7 +313,7 @@ void setup() { @Test void 영상_삭제() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play = playService.addPlayToPlaylist( playlist, @@ -334,7 +336,7 @@ void setup() { @Test void 영상_재정렬() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", isPc); Playlist playlist = playlistService.createPlaylist("default", false, "OTHER", member); Play play1 = playService.addPlayToPlaylist( playlist, diff --git a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java index cf39f9b..c86b25e 100644 --- a/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlaylistServiceIntegrationTest.java @@ -21,14 +21,14 @@ class PlaylistServiceIntegrationTest { @Autowired PlaylistService playlistService; @Autowired - MemberService memberService; + AuthService authService; @Autowired PlayService playService; @Test void 플레이리스트_생성() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", true); String title = "myList"; Boolean isPublic = true; String category = "GAME"; @@ -49,7 +49,7 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_생성_Category이상() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001", true); String title = "myList"; Boolean isPublic = true; String category = "???"; @@ -64,7 +64,7 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_조회() { // given - Member member = memberService.registerNon("devide001"); + Member member = authService.registerNon("devide001",true); String title = "myList"; Boolean isPublic = false; String category = "GAME"; @@ -86,7 +86,7 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_수정() { // given - Member member = memberService.registerNon("devide001"); + Member member = authService.registerNon("devide001",true); Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); String newTitle = "newList"; // when @@ -99,7 +99,7 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_존재X() { // given - Member member = memberService.registerNon("devide001"); + Member member = authService.registerNon("devide001",true); Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); // when Exception e = assertThrows(NotExistPlaylistException.class, () -> playlistService.getPlaylistById(100L)); @@ -111,7 +111,7 @@ class PlaylistServiceIntegrationTest { @Test void 플레이리스트_삭제() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001",true); Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); // when @@ -127,7 +127,7 @@ class PlaylistServiceIntegrationTest { @Test void 썸네일_설정() { // given - Member member = memberService.registerNon("device001"); + Member member = authService.registerNon("device001",true); Playlist playlist = playlistService.createPlaylist("myList", false, "GAME", member); String thumbnail = "thumbnail"; From 3c788118595eae2233de6d71f842ff555bfc0ddd Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 9 Aug 2021 14:35:50 +0900 Subject: [PATCH 20/46] MERGE --- .../java/com/example/youtubedb/controller/MemberController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 5ede405..3909445 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; -// TODO 비회원 로그인 로직 필요 // TODO actuator 관련 이슈 해결 필요! // TODO 404에러 관리할 수 있으면 좋을듯 From bc27d73737fc12fed076b1825e843000321caf78 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Tue, 10 Aug 2021 22:03:47 +0900 Subject: [PATCH 21/46] =?UTF-8?q?1=EC=B0=A8=20MERGE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/youtubedb/config/WebSecurityConfig.java | 2 +- .../com/example/youtubedb/controller/PlaylistController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 485de7d..1256957 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -43,7 +43,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/member/**").permitAll() - .antMatchers("/api/playlist/**").hasRole("ADMIN") + .antMatchers("/api/playlist/**").hasRole("USER") // .anyRequest().authenticated() .anyRequest().permitAll() .and() diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 70a1526..14a8eb6 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -60,7 +60,7 @@ public PlaylistController(PlaylistService playlistService, @GetMapping("/") public ResponseEntity getPlaylist(HttpServletRequest request) { // String loginId = jwtTokenProvider.getUserPk(request.getHeader("X-AUTH-TOKEN")); - String loginId = "member001"; // TODO : jwt에서 가져오는 것으로 수정 필요 + String loginId = "member002"; // TODO : jwt에서 가져오는 것으로 수정 필요 Member member = memberService.findMemberByLoginId(loginId); List playlists = member.getPlaylists(); playlistService.addThumbnail(playlists); From 1a159106b6a529b3cb1f55861c061f4916ae0970 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Wed, 11 Aug 2021 22:07:43 +0900 Subject: [PATCH 22/46] =?UTF-8?q?[MERGE]=20Spring=20Security=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EB=A6=AC,=20Redis=EC=A0=81=EC=9A=A9,=20Re?= =?UTF-8?q?freshToken=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/youtubedb/config/TestConfig.java | 26 +++++++++---------- .../youtubedb/config/jwt/TokenProvider.java | 4 +-- .../controller/MemberController.java | 1 - .../youtubedb/controller/TestController.java | 24 ++++++++--------- .../com/example/youtubedb/domain/Token.java | 6 ++--- .../exception/ControllerExceptionHandler.java | 4 ++- .../exception/RefreshTokenException.java | 7 +++++ .../youtubedb/service/MemberService.java | 11 ++++---- src/main/resources/application.yaml | 12 ++++----- 9 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/exception/RefreshTokenException.java diff --git a/src/main/java/com/example/youtubedb/config/TestConfig.java b/src/main/java/com/example/youtubedb/config/TestConfig.java index 14ddbc6..932cd49 100644 --- a/src/main/java/com/example/youtubedb/config/TestConfig.java +++ b/src/main/java/com/example/youtubedb/config/TestConfig.java @@ -1,13 +1,13 @@ -package com.example.youtubedb.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class TestConfig implements WebMvcConfigurer { - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/hello").setViewName("hello"); - registry.addViewController("/login").setViewName("login"); - } -} +//package com.example.youtubedb.config; +// +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +//@Configuration +//public class TestConfig implements WebMvcConfigurer { +// public void addViewControllers(ViewControllerRegistry registry) { +// registry.addViewController("/hello").setViewName("hello"); +// registry.addViewController("/login").setViewName("login"); +// } +//} diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 065e36a..b4420d3 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -31,8 +31,8 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7; // 7일 + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 30; //30초 // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60; // 7일 1000L * 60 * 60 * 24 * 7; private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 8f4d78b..8f2d15c 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -120,7 +120,6 @@ public ResponseEntity signupReal(@RequestBody MemberRequestDto memberRequestD public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { RequestUtil.checkNeedValue( memberRequestDto.getLoginId(), - memberRequestDto.getPassword(), memberRequestDto.getIsPC()); Member member = memberService.findMemberByLoginId(memberRequestDto.getLoginId()); diff --git a/src/main/java/com/example/youtubedb/controller/TestController.java b/src/main/java/com/example/youtubedb/controller/TestController.java index 76f0863..d0a85a3 100644 --- a/src/main/java/com/example/youtubedb/controller/TestController.java +++ b/src/main/java/com/example/youtubedb/controller/TestController.java @@ -1,12 +1,12 @@ -package com.example.youtubedb.controller; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TestController { - @GetMapping("/admin/test") - public String getTest() { - return "ADMIN TEST"; - } -} +//package com.example.youtubedb.controller; +// +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.RestController; +// +//@RestController +//public class TestController { +// @GetMapping("/admin/test") +// public String getTest() { +// return "ADMIN TEST"; +// } +//} diff --git a/src/main/java/com/example/youtubedb/domain/Token.java b/src/main/java/com/example/youtubedb/domain/Token.java index de40354..88874b3 100644 --- a/src/main/java/com/example/youtubedb/domain/Token.java +++ b/src/main/java/com/example/youtubedb/domain/Token.java @@ -1,9 +1,6 @@ package com.example.youtubedb.domain; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.Date; @@ -13,6 +10,7 @@ public class Token{ private String grantType; private String accessToken; + @Setter private String refreshToken; private Date accessTokenExpiresIn; diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 58ea9a8..0470ee0 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -1,6 +1,7 @@ package com.example.youtubedb.exception; import com.example.youtubedb.dto.BaseResponseFailDto; +import com.example.youtubedb.dto.error.AuthenticationEntryPointFailResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.NotAcceptableFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; @@ -20,7 +21,8 @@ public class ControllerExceptionHandler { NotExistPlayException.class, DuplicateSeqException.class, InvalidSeqException.class, - DoNotMatchPasswordException.class + DoNotMatchPasswordException.class, + RefreshTokenException.class }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/RefreshTokenException.java b/src/main/java/com/example/youtubedb/exception/RefreshTokenException.java new file mode 100644 index 0000000..d815d5b --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/RefreshTokenException.java @@ -0,0 +1,7 @@ +package com.example.youtubedb.exception; + +public class RefreshTokenException extends RuntimeException { + public RefreshTokenException(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 3d86305..c8ab449 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -7,6 +7,7 @@ import com.example.youtubedb.exception.DoNotMatchPasswordException; import com.example.youtubedb.exception.DuplicateMemberException; import com.example.youtubedb.exception.NotExistMemberException; +import com.example.youtubedb.exception.RefreshTokenException; import com.example.youtubedb.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -120,7 +121,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc ) { // 1. Refresh Token 검증 if (!tokenProvider.validateToken(refreshToken)) { - throw new RuntimeException("Refresh Token 이 유효하지 않습니다."); + throw new RefreshTokenException("Refresh Token 이 유효하지 않습니다."); } // 2. Access Token 에서 Member ID(pk) 가져오기 @@ -131,21 +132,21 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc // .orElseThrow(() -> new RuntimeException("로그아웃 된 사용자입니다.")); String redisRefreshToken = stringStringValueOperations.get(authentication.getName()); if(redisRefreshToken == null) { - throw new RuntimeException("다시 로그인이 필요합니다."); + throw new RefreshTokenException("다시 로그인이 필요합니다."); } // 4. Refresh Token 일치하는지 검사 if (!redisRefreshToken.equals(refreshToken)) { - throw new RuntimeException("토큰의 유저 정보가 일치하지 않습니다."); + throw new RefreshTokenException("토큰의 유저 정보가 일치하지 않습니다."); } // 5. 새로운 토큰 생성 Token tokenDto = tokenProvider.generateTokenDto(authentication, isPc); - + tokenDto.setRefreshToken(redisRefreshToken); // 6. 저장소 정보 업데이트 // RefreshToken newRefreshToken = findByIdRefreshToken.updateValue(tokenDto.getRefreshToken()); // refreshTokenRepository.save(newRefreshToken); - stringStringValueOperations.set(authentication.getName(), tokenDto.getRefreshToken(), tokenDto.getAccessTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + stringStringValueOperations.set(authentication.getName(), redisRefreshToken, tokenDto.getAccessTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); // 토큰 발급 return tokenDto; } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1d0375d..d93f023 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -6,11 +6,11 @@ spring: active: local # security 설정 - security: - user: - name: "admin" - password: "pass" - roles: "ADMIN" +# security: +# user: +# name: "admin" +# password: "pass" +# roles: "ADMIN" boot: @@ -42,7 +42,7 @@ management: exposure: include: "*" - base-path: "/admin/monitoring" + base-path: "/monitoring" endpoint: health: From d1b5d4c13bc415d3e7849a97623b4438d8ad7911 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Wed, 11 Aug 2021 22:20:42 +0900 Subject: [PATCH 23/46] 0811 --- .gitignore | 3 ++- .../java/com/example/youtubedb/config/WebSecurityConfig.java | 2 +- .../java/com/example/youtubedb/config/jwt/TokenProvider.java | 2 ++ src/main/java/com/example/youtubedb/service/AuthService.java | 2 ++ .../example/youtubedb/service/CustomUserDetailsService.java | 2 -- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 18eeb13..eb25d5d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ application-prod.yaml -**/src/resources/application.yaml + +**/src/main/resources/application* ### STS ### .apt_generated diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 76612e7..7e6b139 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -43,7 +43,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() - .antMatchers("/api/playlist/**").hasRole("ADMIN") +// .antMatchers("/api/playlist/**").hasRole("ADMIN") .anyRequest().authenticated() .and() diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index eb37eba..ae07027 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -60,6 +60,8 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { // Refresh Token 생성 long expireTime = now + (isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP); Date expireDate = new Date(expireTime); + System.out.println("expireDate = " + expireDate); + System.out.println("expireTime = " + expireTime); String refreshToken = Jwts.builder() .setExpiration(expireDate) .signWith(key, SignatureAlgorithm.HS512) diff --git a/src/main/java/com/example/youtubedb/service/AuthService.java b/src/main/java/com/example/youtubedb/service/AuthService.java index c9a1ecf..242b38b 100644 --- a/src/main/java/com/example/youtubedb/service/AuthService.java +++ b/src/main/java/com/example/youtubedb/service/AuthService.java @@ -68,10 +68,12 @@ public Token login(String loginID, String password, boolean isPc) { // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 UsernamePasswordAuthenticationToken authenticationToken = toAuthentication(loginID, password); + // 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분 // authenticate 메서드가 실행이 될 때 CustomUserDetailsService 에서 만들었던 loadUserByUsername 메서드가 실행됨 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + System.out.println(authentication.getName()); // 3. 인증 정보를 기반으로 JWT 토큰 생성 Token token = tokenProvider.generateTokenDto(authentication, isPc); diff --git a/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java index 6596a96..8b269dc 100644 --- a/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java +++ b/src/main/java/com/example/youtubedb/service/CustomUserDetailsService.java @@ -21,13 +21,11 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - System.out.println("CustomUserDetailsService.loadUserByUsername"); return memberRepository.findByLoginId(username) .map(this::createUserDetails) .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다.")); } private UserDetails createUserDetails(Member member) { - System.out.println("CustomUserDetailsService.createUserDetails"); GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(member.getAuthority().toString()); return new User( From c365edd026ad8369b89b707afc8ff0b42c26167c Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Thu, 12 Aug 2021 13:25:02 +0900 Subject: [PATCH 24/46] =?UTF-8?q?[ADD]=20=EB=B9=84=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/config/WebSecurityConfig.java | 2 +- .../youtubedb/config/jwt/TokenProvider.java | 3 +++ .../youtubedb/controller/PlaylistController.java | 9 ++++++--- .../exception/ControllerExceptionHandler.java | 4 +++- .../exception/OverNomMemberMaxListException.java | 12 ++++++++++++ .../example/youtubedb/service/PlaylistService.java | 14 ++++++++++++++ 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/exception/OverNomMemberMaxListException.java diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 8d09707..1256957 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -43,7 +43,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/member/**").permitAll() -// .antMatchers("/api/playlist/**").hasRole("USER") + .antMatchers("/api/playlist/**").hasRole("USER") // .anyRequest().authenticated() .anyRequest().permitAll() .and() diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 248d933..5e9323e 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -108,16 +108,19 @@ public Authentication getAuthentication(String accessToken) { } public boolean validateToken(String token) { + try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { +// throw new CustomValidationException(); log.info("잘못된 JWT 서명입니다."); } catch (ExpiredJwtException e) { log.info("만료된 JWT 토큰입니다."); } catch (UnsupportedJwtException e) { log.info("지원되지 않는 JWT 토큰입니다."); } catch (IllegalArgumentException e) { +// throw new CustomValidationException(); log.info("JWT 토큰이 잘못되었습니다."); } diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 37bbb67..3cb3432 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -66,7 +66,9 @@ public ResponseEntity getPlaylist(Authentication authentication) { log.info(" loginId = {}", authentication.getName()); String loginId = authentication.getName(); + Member member = memberService.findMemberByLoginId(loginId); + List playlists = member.getPlaylists(); playlistService.addThumbnail(playlists); @@ -92,16 +94,17 @@ public ResponseEntity getPlaylist(Authentication authentication) { @PostMapping("/create") @Operation(summary = "생성", description = "플레이 리스트 생성") public ResponseEntity createPlaylist(@RequestBody PlaylistCreateRequestDto playlistCreateRequestDto, - Authentication authentication) { // 여긴 임시로 loginId 필요 + Authentication authentication) { RequestUtil.checkNeedValue( playlistCreateRequestDto.getTitle(), playlistCreateRequestDto.getIsPublic(), playlistCreateRequestDto.getCategory()); - log.info(" loginId = {}", authentication.getName()); String loginId = authentication.getName(); - Member member = memberService.findMemberByLoginId(loginId); + List playlists = member.getPlaylists(); + + Playlist playlist = playlistService.createPlaylist( playlistCreateRequestDto.getTitle(), playlistCreateRequestDto.getIsPublic(), diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 0470ee0..519bdd4 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -22,7 +22,9 @@ public class ControllerExceptionHandler { DuplicateSeqException.class, InvalidSeqException.class, DoNotMatchPasswordException.class, - RefreshTokenException.class + RefreshTokenException.class, + OverNomMemberMaxListException.class + }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/OverNomMemberMaxListException.java b/src/main/java/com/example/youtubedb/exception/OverNomMemberMaxListException.java new file mode 100644 index 0000000..1817a78 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/OverNomMemberMaxListException.java @@ -0,0 +1,12 @@ +package com.example.youtubedb.exception; + +public class OverNomMemberMaxListException extends RuntimeException { + private static final String MESSAGE = "비회원 playlist 제한을 초과하였습니다."; + public OverNomMemberMaxListException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 6f57504..7cbcbca 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -6,8 +6,10 @@ import com.example.youtubedb.dto.Category; import com.example.youtubedb.exception.NotExistPlaylistException; import com.example.youtubedb.exception.NotExistRequestValueException; +import com.example.youtubedb.exception.OverNomMemberMaxListException; import com.example.youtubedb.repository.PlaylistRepository; import com.example.youtubedb.util.RequestUtil; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,10 +18,13 @@ import java.util.List; @Service +@Slf4j @Transactional public class PlaylistService { private final PlaylistRepository playlistRepository; + private final int NON_MEMBER_MAX_PLAYLISTS = 10; + @Autowired public PlaylistService(PlaylistRepository playlistRepository) { this.playlistRepository = playlistRepository; @@ -27,6 +32,7 @@ public PlaylistService(PlaylistRepository playlistRepository) { public Playlist createPlaylist(String title, Boolean isPublic, String category, Member member) { checkCategory(category); + checkNomMemberCount(member); Playlist playlist = Playlist.builder() .title(title) .isPublic(isPublic) @@ -37,6 +43,14 @@ public Playlist createPlaylist(String title, Boolean isPublic, String category, return playlistRepository.save(playlist); } + private void checkNomMemberCount(Member member) { + if(!member.isMember() && member.getPlaylists().size() >= NON_MEMBER_MAX_PLAYLISTS){ + log.info("isMember, listSize = {} {}",member.isMember(), member.getPlaylists().size()); + throw new OverNomMemberMaxListException(); + } + + } + private void checkCategory(String category) { Category[] categories = Category.values(); int count = 0; From 08a1cb4c97a0806245381db00e25bee9b3bc5928 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Thu, 12 Aug 2021 13:37:48 +0900 Subject: [PATCH 25/46] [FIX] Security config --- .../com/example/youtubedb/config/WebSecurityConfig.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 1256957..0637ca0 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -43,9 +43,9 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/member/**").permitAll() - .antMatchers("/api/playlist/**").hasRole("USER") -// .anyRequest().authenticated() - .anyRequest().permitAll() + .antMatchers("/api/**").authenticated() + .antMatchers("/swagger-ui/**").hasRole("ADMIN") +// .anyRequest().permitAll() .and() .apply(new JwtSecurityConfig(tokenProvider)); From cbe7f61417502243c2aab20874acda4d3ed2ea70 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Thu, 12 Aug 2021 13:57:35 +0900 Subject: [PATCH 26/46] =?UTF-8?q?[FIX]Swagger=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=97=B4=EB=9F=AC=EB=91=90=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/youtubedb/config/WebSecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 0637ca0..2631ae5 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -44,7 +44,7 @@ protected void configure(HttpSecurity http) throws Exception { .authorizeRequests() .antMatchers("/api/member/**").permitAll() .antMatchers("/api/**").authenticated() - .antMatchers("/swagger-ui/**").hasRole("ADMIN") +// .antMatchers("/swagger-ui/**").hasRole("ADMIN") // .anyRequest().permitAll() .and() .apply(new JwtSecurityConfig(tokenProvider)); From cb72c371e093ff9f7aa17f4939efb5aa10d452d1 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Thu, 12 Aug 2021 14:11:52 +0900 Subject: [PATCH 27/46] =?UTF-8?q?[ADD]=20=ED=9A=8C=EC=9B=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B7=9C=EC=B9=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/config/WebSecurityConfig.java | 2 +- .../youtubedb/config/jwt/TokenProvider.java | 5 +- .../controller/MemberController.java | 41 +++++++---- .../youtubedb/controller/PlayController.java | 1 - .../controller/PlaylistController.java | 2 +- .../response/MemberDeleteResponseDto.java | 15 ++++ .../exception/ControllerExceptionHandler.java | 4 +- .../InvalidBlankPasswordException.java | 13 ++++ .../InvalidRegexPasswordException.java | 13 ++++ .../youtubedb/service/MemberService.java | 32 +++++++-- src/main/resources/application-local.yaml | 4 +- .../service/MemberServiceIntegrationTest.java | 68 +++++++++++-------- 12 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/dto/member/response/MemberDeleteResponseDto.java create mode 100644 src/main/java/com/example/youtubedb/exception/InvalidBlankPasswordException.java create mode 100644 src/main/java/com/example/youtubedb/exception/InvalidRegexPasswordException.java diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 8d09707..a22f6b1 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -42,7 +42,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() - .antMatchers("/api/member/**").permitAll() +// .antMatchers("/api/member/**").permitAll() // .antMatchers("/api/playlist/**").hasRole("USER") // .anyRequest().authenticated() .anyRequest().permitAll() diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 248d933..6741aac 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -1,7 +1,6 @@ package com.example.youtubedb.config.jwt; import com.example.youtubedb.domain.Token; -//import com.example.youtubedb.util.RedisUtils; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -31,8 +30,8 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 *30 ; // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60; // 7일 1000L * 60 * 60 * 24 * 7; + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 *30 ; // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7; // 7일 private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 86a5d7c..28ff378 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -8,6 +8,7 @@ import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; import com.example.youtubedb.dto.member.request.MemberRequestDto; import com.example.youtubedb.dto.member.request.NonMemberRequestDto; +import com.example.youtubedb.dto.member.response.MemberDeleteResponseDto; import com.example.youtubedb.dto.member.response.MemberResponseDto; import com.example.youtubedb.dto.member.response.NonMemberResponseDto; import com.example.youtubedb.dto.token.request.TokenReissueRequestDto; @@ -21,12 +22,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -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 org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; // TODO actuator 관련 이슈 해결 필요! // TODO 404에러 관리할 수 있으면 좋을듯 @@ -34,6 +34,7 @@ @Tag(name = "회원 관련 API") @RestController @RequestMapping("/api/member") +@Slf4j public class MemberController { private final MemberService memberService; @@ -133,15 +134,29 @@ public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { return ResponseEntity.ok(responseBody); } - // @DeleteMapping("/delete") -// public ResponseEntity deleteMember(HttpServletRequest request) { -//// String token = token.resolveToken(request); -// String loginId = tokenProvider.resolveToken(token); -// memberService.deleteUserByLoginId(loginId); -// jwtTokenProvider.abandonToken(token); -// -// return ResponseEntity.ok(null); -// } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "회원 삭제 성공", + content = @Content(schema = @Schema(implementation = MemberDeleteResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 아이디 존재 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @DeleteMapping("/delete") + public ResponseEntity deleteMember(Authentication authentication) { + log.info(" authentication = {}", authentication); + log.info(" loginId = {}", authentication.getName()); + String loginId = authentication.getName(); + memberService.deleteUserByLoginId(loginId); + + BaseResponseSuccessDto responseBody = new MemberDeleteResponseDto(loginId); + return ResponseEntity.ok(responseBody); + } + @PostMapping("/reissue") public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequestDto) { RequestUtil.checkNeedValue( diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index ddbed10..3651e4d 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -27,7 +27,6 @@ import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 37bbb67..c90c725 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -61,7 +61,7 @@ public PlaylistController(PlaylistService playlistService, content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "조회", description = "플레이 리스트들 조회") - @GetMapping("/") + @GetMapping public ResponseEntity getPlaylist(Authentication authentication) { log.info(" loginId = {}", authentication.getName()); String loginId = authentication.getName(); diff --git a/src/main/java/com/example/youtubedb/dto/member/response/MemberDeleteResponseDto.java b/src/main/java/com/example/youtubedb/dto/member/response/MemberDeleteResponseDto.java new file mode 100644 index 0000000..80eb98d --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/response/MemberDeleteResponseDto.java @@ -0,0 +1,15 @@ +package com.example.youtubedb.dto.member.response; + +import com.example.youtubedb.dto.BaseResponseSuccessDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class MemberDeleteResponseDto extends BaseResponseSuccessDto { + @Schema(description = "삭제된 사용자 LoginId") + private final String response; + + public MemberDeleteResponseDto(String response) { + this.response = response; + } +} diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 0470ee0..577ab7d 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -22,7 +22,9 @@ public class ControllerExceptionHandler { DuplicateSeqException.class, InvalidSeqException.class, DoNotMatchPasswordException.class, - RefreshTokenException.class + RefreshTokenException.class, + InvalidBlankPasswordException.class, + InvalidRegexPasswordException.class }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/InvalidBlankPasswordException.java b/src/main/java/com/example/youtubedb/exception/InvalidBlankPasswordException.java new file mode 100644 index 0000000..a841586 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/InvalidBlankPasswordException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class InvalidBlankPasswordException extends RuntimeException { + private final static String MESSAGE = "비밀번호에 공백이 존재합니다."; + + public InvalidBlankPasswordException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/exception/InvalidRegexPasswordException.java b/src/main/java/com/example/youtubedb/exception/InvalidRegexPasswordException.java new file mode 100644 index 0000000..ccb50ab --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/InvalidRegexPasswordException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class InvalidRegexPasswordException extends RuntimeException { + private final static String MESSAGE = "비밀번호 규칙을 지키지 못했습니다."; + + public InvalidRegexPasswordException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index a885e83..6580a06 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -4,10 +4,7 @@ import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Authority; import com.example.youtubedb.domain.member.Member; -import com.example.youtubedb.exception.DoNotMatchPasswordException; -import com.example.youtubedb.exception.DuplicateMemberException; -import com.example.youtubedb.exception.NotExistMemberException; -import com.example.youtubedb.exception.RefreshTokenException; +import com.example.youtubedb.exception.*; import com.example.youtubedb.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +26,8 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service @RequiredArgsConstructor @@ -39,7 +38,6 @@ public class MemberService implements UserDetailsService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; private final TokenProvider tokenProvider; -// private final RefreshTokenRepository refreshTokenRepository; private final StringRedisTemplate template; private final ValueOperations stringStringValueOperations; @@ -73,6 +71,7 @@ public Member registerNon(String deviceId, Boolean isPc) { public Member registerReal(String loginId, String password, Boolean isPc) { checkDuplicateMember(loginId); + checkValidPassword(password); Member realMember = Member.builder() .isMember(true) .loginId(loginId) @@ -84,6 +83,29 @@ public Member registerReal(String loginId, String password, Boolean isPc) { return memberRepository.save(realMember); } + private void checkValidPassword(String password) { + int min = 8; + int max = 20; + // 영어, 숫자, 특수문자 포함 min~max글자 + final String regex = "^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + min + "," + max + "})$"; + // 공백 문자 정규식 + final String blankRegex = "(\\s)"; + + Matcher matcher; + + // 공백 체크 + matcher = Pattern.compile(blankRegex).matcher(password); + if (matcher.find()) { + throw new InvalidBlankPasswordException(); + } + + // 정규식 체크 + matcher = Pattern.compile(regex).matcher(password); + if (!matcher.find()) { + throw new InvalidRegexPasswordException(); + } + } + public Token login(String loginID, String password, boolean isPc) { // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 UsernamePasswordAuthenticationToken authenticationToken = toAuthentication(loginID, password); diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 2bd60e3..dcda960 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -7,8 +7,8 @@ spring: ddl-auto: create datasource: - url: jdbc:h2:tcp://localhost/~/spring -# url: jdbc:h2:mem:testdb +# url: jdbc:h2:tcp://localhost/~/spring + url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index 4ca5397..2ae2c92 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -1,15 +1,11 @@ -/* package com.example.youtubedb.service; -import com.example.youtubedb.domain.Member; +import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Member; -import com.example.youtubedb.exception.DoNotMatchPasswordException; -import com.example.youtubedb.exception.DuplicateMemberException; -import com.example.youtubedb.exception.NotExistMemberException; +import com.example.youtubedb.exception.*; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @@ -21,8 +17,6 @@ class MemberServiceIntegrationTest { @Autowired MemberService memberService; - @Autowired - PasswordEncoder passwordEncoder; @Test void 비회원_등록() { @@ -30,13 +24,12 @@ class MemberServiceIntegrationTest { String deviceId = "device001"; // when - Member nonMember = memberService.registerNon(deviceId); + Member nonMember = memberService.registerNon(deviceId, false); // then assertAll( () -> assertThat(nonMember.isMember()).isEqualTo(false), - () -> assertThat(nonMember.getLoginId()).isEqualTo(deviceId), - () -> assertThat(nonMember.getPassword()).isEqualTo(null) + () -> assertThat(nonMember.getLoginId()).isEqualTo(deviceId) ); } @@ -46,8 +39,8 @@ class MemberServiceIntegrationTest { String deviceId = "device001"; // when - memberService.registerNon(deviceId); - Exception e = assertThrows(DuplicateMemberException.class, () -> memberService.registerNon(deviceId)); + memberService.registerNon(deviceId, false); + Exception e = assertThrows(DuplicateMemberException.class, () -> memberService.registerNon(deviceId, false)); // then assertThat(e.getMessage()).isEqualTo(DuplicateMemberException.getErrorMessage()); @@ -65,8 +58,7 @@ class MemberServiceIntegrationTest { // then assertAll( () -> assertThat(result.isMember()).isEqualTo(false), - () -> assertThat(result.getLoginId()).isEqualTo(deviceId), - () -> assertThat(result.getPassword()).isEqualTo(null) + () -> assertThat(result.getLoginId()).isEqualTo(deviceId) ); } @@ -86,7 +78,7 @@ class MemberServiceIntegrationTest { void 회원_비회원_삭제() { // given String deviceId = "device001"; - Member nonMember = memberService.registerNon(deviceId); + Member nonMember = memberService.registerNon(deviceId, false); // when memberService.deleteUserByLoginId(deviceId); @@ -100,7 +92,7 @@ class MemberServiceIntegrationTest { void 회원가입() { // given String loginId = "helloMan"; - String password = "hello123"; + String password = "hello123*"; // when Member member = memberService.registerReal(loginId, password, false); @@ -115,23 +107,45 @@ class MemberServiceIntegrationTest { ); } + @Test + void 회원가입_비밀번호_공백() { + // given + String loginId = "helloMan"; + String password = "he llo"; + + // when + Exception e = assertThrows(InvalidBlankPasswordException.class, () -> memberService.registerReal(loginId, password, false)); + + // then + assertThat(e.getMessage()).isEqualTo(InvalidBlankPasswordException.getErrorMessage()); + } + + @Test + void 회원가입_비밀번호규칙X() { + // given + String loginId = "helloMan"; + String password = "hello"; + + // when + Exception e = assertThrows(InvalidRegexPasswordException.class, () -> memberService.registerReal(loginId, password, false)); + + // then + assertThat(e.getMessage()).isEqualTo(InvalidRegexPasswordException.getErrorMessage()); + } + @Test void 로그인() { // given String loginId = "helloMan"; String password = "hello123"; + String BEARER_TYPE = "bearer"; // when Member member = memberService.registerReal(loginId, password, false); - Member loginMember = memberService.login(loginId, password, false); + Token token = memberService.login(loginId, password, false); // then - assertAll( - () -> assertThat(loginMember.getId()).isEqualTo(member.getId()), - () -> assertThat(loginMember.getLoginId()).isEqualTo(member.getLoginId()), - () -> assertThat(loginMember.getPassword()).isEqualTo(member.getPassword()), - () -> assertThat(loginMember.isMember()).isEqualTo(member.isMember()) - ); + assertThat(token.getGrantType()).isEqualTo(BEARER_TYPE); } @Test @@ -140,12 +154,12 @@ class MemberServiceIntegrationTest { String loginId = "helloMan"; String password = "hello123"; String otherPassword = "bye123"; - Member member = memberService.register(loginId, password); + Member member = memberService.registerReal(loginId, password, false); // when - Exception e = assertThrows(DoNotMatchPasswordException.class, () -> memberService.login(loginId, otherPassword)); + Exception e = assertThrows(DoNotMatchPasswordException.class, () -> memberService.login(loginId, otherPassword, false)); // then assertThat(e.getMessage()).isEqualTo(DoNotMatchPasswordException.getErrorMessage()); } -}*/ +} From 19637849af7eca94f46b484cdc6c1672ce2d0e97 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Thu, 12 Aug 2021 17:16:39 +0900 Subject: [PATCH 28/46] =?UTF-8?q?[FIX]=20RefreshTokenExpire=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/youtubedb/config/RedisConfig.java | 2 ++ .../com/example/youtubedb/config/jwt/TokenProvider.java | 3 +-- src/main/java/com/example/youtubedb/domain/Token.java | 4 +++- .../java/com/example/youtubedb/service/MemberService.java | 7 ++----- src/main/resources/application-local.yaml | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/RedisConfig.java b/src/main/java/com/example/youtubedb/config/RedisConfig.java index c93a647..879c4fe 100644 --- a/src/main/java/com/example/youtubedb/config/RedisConfig.java +++ b/src/main/java/com/example/youtubedb/config/RedisConfig.java @@ -19,6 +19,8 @@ @EnableRedisRepositories public class RedisConfig { + // TODO Redis 현재 DEAMON으로 돌려놓음 -> SYSTEMD로 추후 전환 필요 + // private final ObjectMapper objectMapper; // private final RedisProperties redisProperties; @Value("${spring.redis.host}") diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 5e9323e..f2126da 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -77,13 +77,12 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { // redisUtils.put("1", "2", expireTime); // redisUtils.put(authentication.getName(), refreshToken, expireTime); - stringStringValueOperations.set(authentication.getName(), refreshToken, expireTime, TimeUnit.MILLISECONDS); // redis에 refreshToken 보관 - return Token.builder() .grantType(BEARER_TYPE) .accessToken(accessToken) .accessTokenExpiresIn(accessTokenExpiresIn) .refreshToken(refreshToken) + .refreshTokenExpiresIn(expireDate) .build(); } diff --git a/src/main/java/com/example/youtubedb/domain/Token.java b/src/main/java/com/example/youtubedb/domain/Token.java index 88874b3..5f16cb6 100644 --- a/src/main/java/com/example/youtubedb/domain/Token.java +++ b/src/main/java/com/example/youtubedb/domain/Token.java @@ -13,13 +13,15 @@ public class Token{ @Setter private String refreshToken; private Date accessTokenExpiresIn; + private Date refreshTokenExpiresIn; @Builder - public Token(String grantType, String accessToken, String refreshToken, Date accessTokenExpiresIn) { + public Token(String grantType, String accessToken, String refreshToken, Date accessTokenExpiresIn, Date refreshTokenExpiresIn) { this.grantType = grantType; this.accessToken = accessToken; this.refreshToken = refreshToken; this.accessTokenExpiresIn = accessTokenExpiresIn; + this.refreshTokenExpiresIn = refreshTokenExpiresIn; } } \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index a885e83..8746dee 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -106,7 +106,7 @@ public Token login(String loginID, String password, boolean isPc) { //redis 활용 중 - 현재 저장만, expire 못함 // RedisUtils.put(authentication.getName(), token.getRefreshToken(), token.re); - stringStringValueOperations.set(authentication.getName(), token.getRefreshToken(), token.getAccessTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + stringStringValueOperations.set(authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); // 5. 토큰 발급 return token; @@ -143,10 +143,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc // 5. 새로운 토큰 생성 Token tokenDto = tokenProvider.generateTokenDto(authentication, isPc); tokenDto.setRefreshToken(redisRefreshToken); - // 6. 저장소 정보 업데이트 -// RefreshToken newRefreshToken = findByIdRefreshToken.updateValue(tokenDto.getRefreshToken()); -// refreshTokenRepository.save(newRefreshToken); - stringStringValueOperations.set(authentication.getName(), redisRefreshToken, tokenDto.getAccessTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + // 토큰 발급 return tokenDto; } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 2bd60e3..1ebbe61 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -16,4 +16,5 @@ spring: redis: host: 127.0.0.1 port: 6379 + password: From 157874e43e6743622eba44532a2025168f99b51a Mon Sep 17 00:00:00 2001 From: oh980225 Date: Thu, 12 Aug 2021 17:20:27 +0900 Subject: [PATCH 29/46] =?UTF-8?q?[ADD]=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberController.java | 31 +++++++++++++------ .../dto/token/resposne/TokenResponseDto.java | 2 +- .../youtubedb/service/MemberService.java | 7 ++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 28ff378..5f92aaf 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -39,15 +39,12 @@ public class MemberController { private final MemberService memberService; private final PlaylistService playlistService; - private final TokenProvider tokenProvider; @Autowired public MemberController(MemberService memberService, - PlaylistService playlistService, - TokenProvider tokenProvider) { + PlaylistService playlistService) { this.memberService = memberService; this.playlistService = playlistService; - this.tokenProvider = tokenProvider; } @ApiResponses(value = { @@ -60,7 +57,7 @@ public MemberController(MemberService memberService, "2. 필요값 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", - description = "서버 에러", + description = "* 서버 에러", content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "가입", description = "비회원 가입") @@ -87,7 +84,7 @@ public ResponseEntity signupNon(@RequestBody NonMemberRequestDto nonMemberReq "2. 필요값 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", - description = "서버 에러", + description = "* 서버 에러", content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) @Operation(summary = "가입", description = "회원 가입") @@ -116,10 +113,10 @@ public ResponseEntity signupReal(@RequestBody MemberRequestDto memberRequestD "3. 필요값 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", - description = "서버 에러", + description = "* 서버 에러", content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) - @Operation(summary = "로그인", description = "회원 로그인") + @Operation(summary = "로그인(회원+비회원)", description = "비회원+회원 로그인") @PostMapping("/login") public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { RequestUtil.checkNeedValue( @@ -143,9 +140,10 @@ public ResponseEntity login(@RequestBody MemberRequestDto memberRequestDto) { "1. 아이디 존재 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", - description = "서버 에러", + description = "* 서버 에러", content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) }) + @Operation(summary = "회원 삭제", description = "회원 삭제") @DeleteMapping("/delete") public ResponseEntity deleteMember(Authentication authentication) { log.info(" authentication = {}", authentication); @@ -157,6 +155,21 @@ public ResponseEntity deleteMember(Authentication authentication) { return ResponseEntity.ok(responseBody); } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "토큰 재발급 성공", + content = @Content(schema = @Schema(implementation = TokenResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. refresh 토큰 유효X\n" + + "2. refresh 토큰 기간 만료\n" + + "3. refresh 토큰 불일치\n", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "* 서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "토큰 재발급", description = "access토큰 재발급") @PostMapping("/reissue") public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequestDto) { RequestUtil.checkNeedValue( diff --git a/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java b/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java index 7b7709d..1006692 100644 --- a/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java +++ b/src/main/java/com/example/youtubedb/dto/token/resposne/TokenResponseDto.java @@ -7,7 +7,7 @@ @Getter public class TokenResponseDto extends BaseResponseSuccessDto { - @Schema(description = "토큰", example = "token123") + @Schema(description = "토큰") private final Token response; public TokenResponseDto(Token response) { diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 6580a06..af825e8 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -137,10 +137,7 @@ public Token login(String loginID, String password, boolean isPc) { } } - @Transactional - public Token reissue(String accessToken, String refreshToken, boolean isPc -// TokenReissueRequestDto tokenRequestDto - ) { + public Token reissue(String accessToken, String refreshToken, boolean isPc) { // 1. Refresh Token 검증 if (!tokenProvider.validateToken(refreshToken)) { throw new RefreshTokenException("Refresh Token 이 유효하지 않습니다."); @@ -168,7 +165,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc // 6. 저장소 정보 업데이트 // RefreshToken newRefreshToken = findByIdRefreshToken.updateValue(tokenDto.getRefreshToken()); // refreshTokenRepository.save(newRefreshToken); - stringStringValueOperations.set(authentication.getName(), redisRefreshToken, tokenDto.getAccessTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + stringStringValueOperations.set(authentication.getName(), redisRefreshToken); // 토큰 발급 return tokenDto; } From bb11e01bf0206332aadda61e68834527444b75be Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Thu, 12 Aug 2021 20:51:45 +0900 Subject: [PATCH 30/46] [FIX] PlayService --- .../com/example/youtubedb/controller/PlayController.java | 8 ++++++-- src/main/resources/application-local.yaml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index 3651e4d..1c05db1 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -62,11 +62,15 @@ public PlayController(PlayService playService, }) @GetMapping("/list/{playlistId}") @Operation(summary = "조회", description = "영상 목록 조회") - public ResponseEntity getPlays(@Parameter @PathVariable("playlistId") Long playlistId) { + public ResponseEntity getPlays(@Parameter @PathVariable("playlistId") Long playlistId, + Authentication authentication) { + RequestUtil.checkNeedValue(playlistId); + log.info(" loginId = {}", authentication.getName()); + String loginId = authentication.getName(); Playlist playlist = playlistService.getPlaylistById(playlistId); - List plays = playService.getPlaysInPlaylist(playlist, "loginId?"); + List plays = playService.getPlaysInPlaylist(playlist, loginId); BaseResponseSuccessDto responseBody = new PlaysGetResponseDto(plays); diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 17acef2..1ebbe61 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -7,8 +7,8 @@ spring: ddl-auto: create datasource: -# url: jdbc:h2:tcp://localhost/~/spring - url: jdbc:h2:mem:testdb + url: jdbc:h2:tcp://localhost/~/spring +# url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver From 1f1ca33850bc6bf291cb31071e3c4681bdbd359b Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Fri, 13 Aug 2021 09:42:04 +0900 Subject: [PATCH 31/46] [FIX] cors --- .gitignore | 1 + .../com/example/youtubedb/config/WebSecurityConfig.java | 7 ++++--- src/main/resources/application-local.yaml | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index eb25d5d..768fb9e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/ application-prod.yaml **/src/main/resources/application* +**/src/main/resources/application-local.yaml ### STS ### .apt_generated diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 1e5cfad..02b7172 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -13,6 +13,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.cors.CorsUtils; @Configuration @RequiredArgsConstructor @@ -42,18 +43,18 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() - + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .antMatchers("/api/member/**").permitAll() .antMatchers("/api/**").authenticated() // .antMatchers("/swagger-ui/**").hasRole("ADMIN") // .anyRequest().permitAll() - + .and() + .cors() .and() .apply(new JwtSecurityConfig(tokenProvider)); - // .authorizeRequests() // .antMatchers("/", "/applications").authenticated() // .antMatchers("/admin").hasRole("ADMIN") diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 1ebbe61..741868c 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -6,6 +6,10 @@ spring: hibernate: ddl-auto: create + + + + datasource: url: jdbc:h2:tcp://localhost/~/spring # url: jdbc:h2:mem:testdb From 62da71965111428f72562ff73391e990c0057a25 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Fri, 13 Aug 2021 09:58:56 +0900 Subject: [PATCH 32/46] Cors --- .../com/example/youtubedb/config/SpringConfig.java | 10 ---------- .../example/youtubedb/config/WebSecurityConfig.java | 6 ++++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/SpringConfig.java b/src/main/java/com/example/youtubedb/config/SpringConfig.java index 814aff3..ee80e5f 100644 --- a/src/main/java/com/example/youtubedb/config/SpringConfig.java +++ b/src/main/java/com/example/youtubedb/config/SpringConfig.java @@ -6,16 +6,6 @@ @Configuration public class SpringConfig implements WebMvcConfigurer { -// @Bean -// public PasswordEncoder passwordEncoder() { -// return PasswordEncoderFactories.createDelegatingPasswordEncoder(); -// } - -// @Bean -// public AuthenticationEntryPoint authenticationEntryPoint() { -// return new AuthenticationEntryPointImpl(); -// } - @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 1e5cfad..51bc58c 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -13,6 +13,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.cors.CorsUtils; @Configuration @RequiredArgsConstructor @@ -42,12 +43,13 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() - + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .antMatchers("/api/member/**").permitAll() .antMatchers("/api/**").authenticated() // .antMatchers("/swagger-ui/**").hasRole("ADMIN") // .anyRequest().permitAll() - + .and() + .cors() .and() .apply(new JwtSecurityConfig(tokenProvider)); From 03f389db2e0793e6886423b0f5f02cfc613df3eb Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Fri, 13 Aug 2021 22:00:48 +0900 Subject: [PATCH 33/46] =?UTF-8?q?[FIX]=20=EB=A7=8C=EB=A3=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=A1=9C=EC=A7=81=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/youtubedb/config/jwt/TokenProvider.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 7ff8220..20fb730 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -35,12 +35,9 @@ public class TokenProvider { private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; -// private final RedisUtils redisUtils; - private final StringRedisTemplate template; @Autowired - public TokenProvider(@Value("${jwt.secret}") String secretKey, StringRedisTemplate template) { - this.template = template; + public TokenProvider(@Value("${jwt.secret}") String secretKey) { byte[] keyBytes = Decoders.BASE64.decode(secretKey); this.key = Keys.hmacShaKeyFor(keyBytes); } @@ -63,7 +60,7 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { .compact(); // Refresh Token 생성 - long expireTime = now + (isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP); + long expireTime = isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP; Date expireDate = new Date(expireTime); System.out.println("expireDate = " + expireDate); System.out.println("expireTime = " + expireTime); @@ -72,7 +69,6 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { .signWith(key, SignatureAlgorithm.HS512) .compact(); - ValueOperations stringStringValueOperations = template.opsForValue(); // redisUtils.put("1", "2", expireTime); // redisUtils.put(authentication.getName(), refreshToken, expireTime); From 4f385347c2a18b1c249319fc4ad25b0c99bf47d4 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sat, 14 Aug 2021 15:41:26 +0900 Subject: [PATCH 34/46] [FIX] Refresh token redis structure --- .../youtubedb/config/jwt/TokenProvider.java | 2 +- .../youtubedb/domain/RefreshToken.java | 55 ++++++++----------- .../repository/RefreshTokenRepository.java | 12 ---- .../youtubedb/service/MemberService.java | 38 +++++++------ 4 files changed, 46 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index 20fb730..e0266c2 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -61,7 +61,7 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { // Refresh Token 생성 long expireTime = isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP; - Date expireDate = new Date(expireTime); + Date expireDate = new Date(now + expireTime); System.out.println("expireDate = " + expireDate); System.out.println("expireTime = " + expireTime); String refreshToken = Jwts.builder() diff --git a/src/main/java/com/example/youtubedb/domain/RefreshToken.java b/src/main/java/com/example/youtubedb/domain/RefreshToken.java index 60db437..a51fb89 100644 --- a/src/main/java/com/example/youtubedb/domain/RefreshToken.java +++ b/src/main/java/com/example/youtubedb/domain/RefreshToken.java @@ -1,31 +1,24 @@ -//package com.example.youtubedb.domain; -// -//import lombok.Builder; -//import lombok.Getter; -//import lombok.NoArgsConstructor; -// -//import javax.persistence.Entity; -//import javax.persistence.Id; -//import javax.persistence.Table; -// -//@Getter -//@NoArgsConstructor -//@Entity -//@Table(name = "refresh_token") -//public class RefreshToken { -// -// @Id -// private String key; -// private String value; -// -// public RefreshToken updateValue(String token) { -// this.value = token; -// return this; -// } -// -// @Builder -// public RefreshToken(String key, String value) { -// this.key = key; -// this.value = value; -// } -//} +package com.example.youtubedb.domain; + + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@Getter +@RedisHash("refresh") +public class RefreshToken { + + @Id + private String pckey; + private String phonekey; + + + + @Builder + public RefreshToken(String pckey, String phonekey) { + this.pckey = pckey; + this.phonekey = phonekey; + } +} diff --git a/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java b/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java deleted file mode 100644 index 0b794f9..0000000 --- a/src/main/java/com/example/youtubedb/repository/RefreshTokenRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -//package com.example.youtubedb.repository; -// -//import com.example.youtubedb.domain.RefreshToken; -//import org.springframework.data.jpa.repository.JpaRepository; -//import org.springframework.stereotype.Repository; -// -//import java.util.Optional; -// -//@Repository -//public interface RefreshTokenRepository extends JpaRepository { -// Optional findByKey(String key); -//} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index f3946e3..2f5154c 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -1,13 +1,17 @@ package com.example.youtubedb.service; import com.example.youtubedb.config.jwt.TokenProvider; +import com.example.youtubedb.domain.RefreshToken; import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Authority; import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.exception.*; import com.example.youtubedb.repository.MemberRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.authentication.BadCredentialsException; @@ -32,6 +36,7 @@ @Service @RequiredArgsConstructor @Transactional +@Slf4j public class MemberService implements UserDetailsService { private final AuthenticationManagerBuilder authenticationManagerBuilder; @@ -118,19 +123,15 @@ public Token login(String loginID, String password, boolean isPc) { // 3. 인증 정보를 기반으로 JWT 토큰 생성 Token token = tokenProvider.generateTokenDto(authentication, isPc); - // 4. RefreshToken 저장 -// RefreshToken refreshToken = RefreshToken.builder() -// .key(authentication.getName()) -// .value(token.getRefreshToken()) -// .build(); + if(isPc) { + stringStringValueOperations.set("PC"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + }else { + stringStringValueOperations.set("APP"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + } - //redis 활용 중 - 현재 저장만, expire 못함 -// RedisUtils.put(authentication.getName(), token.getRefreshToken(), token.re); - stringStringValueOperations.set(authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); - - // 5. 토큰 발급 + // 4. 토큰 발급 return token; } catch (BadCredentialsException e) { throw new DoNotMatchPasswordException(); @@ -138,7 +139,7 @@ public Token login(String loginID, String password, boolean isPc) { } @Transactional - public Token reissue(String accessToken, String refreshToken, boolean isPc + public Token reissue(String accessToken, String refreshToken, boolean isPC // TokenReissueRequestDto tokenRequestDto ) { // 1. Refresh Token 검증 @@ -149,10 +150,14 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc // 2. Access Token 에서 Member ID(pk) 가져오기 Authentication authentication = tokenProvider.getAuthentication(accessToken); - // 3. 저장소에서 Member ID 를 기반으로 Refresh Token 값 가져옴 -// RefreshToken findByIdRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) -// .orElseThrow(() -> new RuntimeException("로그아웃 된 사용자입니다.")); - String redisRefreshToken = stringStringValueOperations.get(authentication.getName()); + //3. Redis에서 파일 불러옴 + String redisRefreshToken; + if(isPC) { + redisRefreshToken = stringStringValueOperations.get("PC"+authentication.getName()); + }else { + redisRefreshToken = stringStringValueOperations.get("APP"+authentication.getName()); + } + if(redisRefreshToken == null) { throw new RefreshTokenException("다시 로그인이 필요합니다."); } @@ -163,7 +168,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPc } // 5. 새로운 토큰 생성 - Token tokenDto = tokenProvider.generateTokenDto(authentication, isPc); + Token tokenDto = tokenProvider.generateTokenDto(authentication, isPC); tokenDto.setRefreshToken(redisRefreshToken); // 토큰 발급 @@ -197,7 +202,6 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다.")); } private UserDetails createUserDetails(Member member) { - System.out.println("CustomUserDetailsService.createUserDetails"); GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(member.getAuthority().toString()); return new User( From b02c10162099eeab82b811575a6efc1863eb042a Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sat, 14 Aug 2021 15:41:49 +0900 Subject: [PATCH 35/46] [FIX] Refresh token redis structure --- .../youtubedb/domain/RefreshToken.java | 24 ------------------- .../youtubedb/service/MemberService.java | 3 --- 2 files changed, 27 deletions(-) delete mode 100644 src/main/java/com/example/youtubedb/domain/RefreshToken.java diff --git a/src/main/java/com/example/youtubedb/domain/RefreshToken.java b/src/main/java/com/example/youtubedb/domain/RefreshToken.java deleted file mode 100644 index a51fb89..0000000 --- a/src/main/java/com/example/youtubedb/domain/RefreshToken.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.youtubedb.domain; - - -import lombok.Builder; -import lombok.Getter; -import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.RedisHash; - -@Getter -@RedisHash("refresh") -public class RefreshToken { - - @Id - private String pckey; - private String phonekey; - - - - @Builder - public RefreshToken(String pckey, String phonekey) { - this.pckey = pckey; - this.phonekey = phonekey; - } -} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 2f5154c..a495960 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -1,7 +1,6 @@ package com.example.youtubedb.service; import com.example.youtubedb.config.jwt.TokenProvider; -import com.example.youtubedb.domain.RefreshToken; import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Authority; import com.example.youtubedb.domain.member.Member; @@ -10,8 +9,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.authentication.BadCredentialsException; From 39929e712dd139a9b2e86653e5d4bfa4a745bbee Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sat, 14 Aug 2021 15:45:56 +0900 Subject: [PATCH 36/46] [FIX] Refresh token redis structure --- .../java/com/example/youtubedb/service/MemberService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index a495960..0469b5e 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -136,9 +136,8 @@ public Token login(String loginID, String password, boolean isPc) { } @Transactional - public Token reissue(String accessToken, String refreshToken, boolean isPC -// TokenReissueRequestDto tokenRequestDto - ) { + public Token reissue(String accessToken, String refreshToken, boolean isPC) + { // 1. Refresh Token 검증 if (!tokenProvider.validateToken(refreshToken)) { throw new RefreshTokenException("Refresh Token 이 유효하지 않습니다."); From 8673c0ca629407d4c31311258debd599bdc91f14 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sat, 14 Aug 2021 15:57:44 +0900 Subject: [PATCH 37/46] [FIX] Refresh token redis expireTime --- .../youtubedb/config/jwt/TokenProvider.java | 4 +--- .../youtubedb/domain/member/Member.java | 6 +++--- .../youtubedb/service/MemberService.java | 20 ++++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index e0266c2..155488b 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -60,10 +60,8 @@ public Token generateTokenDto(Authentication authentication, boolean isPc) { .compact(); // Refresh Token 생성 - long expireTime = isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP; - Date expireDate = new Date(now + expireTime); + Date expireDate = new Date(now + (isPc? REFRESH_TOKEN_EXPIRE_TIME_PC : REFRESH_TOKEN_EXPIRE_TIME_APP)); System.out.println("expireDate = " + expireDate); - System.out.println("expireTime = " + expireTime); String refreshToken = Jwts.builder() .setExpiration(expireDate) .signWith(key, SignatureAlgorithm.HS512) diff --git a/src/main/java/com/example/youtubedb/domain/member/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java index 5be9193..8a8e00e 100644 --- a/src/main/java/com/example/youtubedb/domain/member/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -31,18 +31,18 @@ public class Member extends BaseEntity { private Authority authority; @Transient - private boolean isPc; + private boolean isPC; @JsonIgnore @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List playlists = new ArrayList<>(); @Builder - public Member(String loginId, String password, boolean isMember, Authority authority, boolean isPc){ + public Member(String loginId, String password, boolean isMember, Authority authority, boolean isPC){ this.loginId = loginId; this.password = password; this.isMember = isMember; this.authority = authority; - this.isPc = isPc; + this.isPC = isPC; } } diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 0469b5e..5860d1f 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Collections; +import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,7 +58,7 @@ public MemberService(AuthenticationManagerBuilder authenticationManagerBuilder, this.stringStringValueOperations = template.opsForValue(); } - public Member registerNon(String deviceId, Boolean isPc) { + public Member registerNon(String deviceId, Boolean isPC) { checkDuplicateMember(deviceId); Member nonMember = Member.builder() @@ -65,13 +66,13 @@ public Member registerNon(String deviceId, Boolean isPc) { .loginId(deviceId) .authority(Authority.ROLE_USER) .password(passwordEncoder.encode(deviceId)) - .isPc(isPc) + .isPC(isPC) .build(); return memberRepository.save(nonMember); } - public Member registerReal(String loginId, String password, Boolean isPc) { + public Member registerReal(String loginId, String password, Boolean isPC) { checkDuplicateMember(loginId); checkValidPassword(password); Member realMember = Member.builder() @@ -79,7 +80,7 @@ public Member registerReal(String loginId, String password, Boolean isPc) { .loginId(loginId) .authority(Authority.ROLE_USER) .password(passwordEncoder.encode(password)) - .isPc(isPc) + .isPC(isPC) .build(); return memberRepository.save(realMember); @@ -108,7 +109,7 @@ private void checkValidPassword(String password) { } } - public Token login(String loginID, String password, boolean isPc) { + public Token login(String loginID, String password, boolean isPC) { // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 UsernamePasswordAuthenticationToken authenticationToken = toAuthentication(loginID, password); @@ -118,14 +119,15 @@ public Token login(String loginID, String password, boolean isPc) { Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); System.out.println("authentication = " + authentication.getName()); // 3. 인증 정보를 기반으로 JWT 토큰 생성 - Token token = tokenProvider.generateTokenDto(authentication, isPc); + Token token = tokenProvider.generateTokenDto(authentication, isPC); - if(isPc) { - stringStringValueOperations.set("PC"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + long now = (new Date()).getTime(); + if(isPC) { + stringStringValueOperations.set("PC"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime()- now, TimeUnit.MILLISECONDS); }else { - stringStringValueOperations.set("APP"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime(), TimeUnit.MILLISECONDS); + stringStringValueOperations.set("APP"+authentication.getName(), token.getRefreshToken(), token.getRefreshTokenExpiresIn().getTime() -now, TimeUnit.MILLISECONDS); } // 4. 토큰 발급 From b7ad771ad0a90fb0d2999918f52d7421cb7b23f1 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Tue, 17 Aug 2021 14:14:18 +0900 Subject: [PATCH 38/46] =?UTF-8?q?[ADD]=20S3=201=EC=B0=A8=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../youtubedb/YoutubeDbApplication.java | 2 - .../example/youtubedb/config/RedisConfig.java | 7 -- .../controller/MemberController.java | 22 ++++++- .../youtubedb/domain/member/Member.java | 7 +- .../com/example/youtubedb/s3/S3Uploader.java | 65 +++++++++++++++++++ .../youtubedb/service/MemberService.java | 4 ++ src/main/resources/application-local.yaml | 14 ++-- 8 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/s3/S3Uploader.java diff --git a/build.gradle b/build.gradle index c73fc7e..afd73b4 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.modelmapper:modelmapper:2.3.6' + // aws cloud + implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index b4e2386..ec30892 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -9,9 +9,7 @@ @EnableAdminServer @EnableJpaAuditing public class YoutubeDbApplication { - public static void main(String[] args) { SpringApplication.run(YoutubeDbApplication.class, args); } - } diff --git a/src/main/java/com/example/youtubedb/config/RedisConfig.java b/src/main/java/com/example/youtubedb/config/RedisConfig.java index 879c4fe..179354a 100644 --- a/src/main/java/com/example/youtubedb/config/RedisConfig.java +++ b/src/main/java/com/example/youtubedb/config/RedisConfig.java @@ -1,18 +1,12 @@ package com.example.youtubedb.config; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import org.modelmapper.ModelMapper; -import org.modelmapper.convention.MatchingStrategies; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; -import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration //@RequiredArgsConstructor @@ -57,5 +51,4 @@ public RedisConnectionFactory redisConnectionFactory() { redisTemplate.setConnectionFactory(redisConnectionFactory()); return redisTemplate; } - } diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 5f92aaf..52fa3a3 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -1,6 +1,5 @@ package com.example.youtubedb.controller; -import com.example.youtubedb.config.jwt.TokenProvider; import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.dto.BaseResponseSuccessDto; @@ -13,6 +12,7 @@ import com.example.youtubedb.dto.member.response.NonMemberResponseDto; import com.example.youtubedb.dto.token.request.TokenReissueRequestDto; import com.example.youtubedb.dto.token.resposne.TokenResponseDto; +import com.example.youtubedb.s3.S3Uploader; import com.example.youtubedb.service.MemberService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; @@ -27,6 +27,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; // TODO actuator 관련 이슈 해결 필요! // TODO 404에러 관리할 수 있으면 좋을듯 @@ -39,12 +42,15 @@ public class MemberController { private final MemberService memberService; private final PlaylistService playlistService; + private final S3Uploader s3Uploader; @Autowired public MemberController(MemberService memberService, - PlaylistService playlistService) { + PlaylistService playlistService, + S3Uploader s3Uploader) { this.memberService = memberService; this.playlistService = playlistService; + this.s3Uploader = s3Uploader; } @ApiResponses(value = { @@ -181,4 +187,16 @@ public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequ BaseResponseSuccessDto responseBody = new TokenResponseDto(token); return ResponseEntity.ok(responseBody); } + + @PostMapping("/upload") + @ResponseBody + public ResponseEntity upload(@RequestParam("img") MultipartFile img, @RequestParam("loginId") String loginId) throws IOException { + Member member = memberService.findMemberByLoginId(loginId); + String profileImg = s3Uploader.upload(img, "static"); + memberService.setProfileImg(member, profileImg); + + BaseResponseSuccessDto responseBody = new MemberResponseDto(member); + + return ResponseEntity.ok(responseBody); + } } diff --git a/src/main/java/com/example/youtubedb/domain/member/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java index 8a8e00e..e26fee0 100644 --- a/src/main/java/com/example/youtubedb/domain/member/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -3,10 +3,7 @@ import com.example.youtubedb.domain.BaseEntity; import com.example.youtubedb.domain.Playlist; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -26,6 +23,8 @@ public class Member extends BaseEntity { @JsonIgnore private String password; private boolean isMember; + @Setter + private String profileImg = null; @Enumerated(value = EnumType.STRING) private Authority authority; diff --git a/src/main/java/com/example/youtubedb/s3/S3Uploader.java b/src/main/java/com/example/youtubedb/s3/S3Uploader.java new file mode 100644 index 0000000..34ae91e --- /dev/null +++ b/src/main/java/com/example/youtubedb/s3/S3Uploader.java @@ -0,0 +1,65 @@ +package com.example.youtubedb.s3; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor +@Component +public class S3Uploader { + private final AmazonS3Client amazonS3Client; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String upload(MultipartFile multipartFile, String dirName) throws IOException { + File uploadFile = convert(multipartFile) + .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다.")); + return upload(uploadFile, dirName); + } + + private String upload(File uploadFile, String dirName) { + String fileName = dirName + "/" + uploadFile.getName(); + String uploadImageUrl = putS3(uploadFile, fileName); + removeNewFile(uploadFile); + return uploadImageUrl; + } + + private String putS3(File uploadFile, String fileName) { + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead)); + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + private void removeNewFile(File targetFile) { + if (targetFile.delete()) { + log.info("파일이 삭제되었습니다."); + } else { + log.info("파일이 삭제되지 못했습니다."); + } + } + + private Optional convert(MultipartFile file) throws IOException { + System.out.println(file.getOriginalFilename()); + File convertFile = new File(file.getOriginalFilename()); + if(convertFile.createNewFile()) { + try (FileOutputStream fos = new FileOutputStream(convertFile)) { + fos.write(file.getBytes()); + } + System.out.println(convertFile); + return Optional.of(convertFile); + } + + return Optional.empty(); + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 6fd88cf..cdc1968 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -208,4 +208,8 @@ private UserDetails createUserDetails(Member member) { Collections.singleton(grantedAuthority) ); } + + public void setProfileImg(Member member, String profileImg) { + member.setProfileImg(profileImg); + } } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 741868c..f8b11d0 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -6,13 +6,9 @@ spring: hibernate: ddl-auto: create - - - - datasource: - url: jdbc:h2:tcp://localhost/~/spring -# url: jdbc:h2:mem:testdb +# url: jdbc:h2:tcp://localhost/~/spring + url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver @@ -21,4 +17,10 @@ spring: host: 127.0.0.1 port: 6379 password: +# multipart 용량 설정 + servlet: + multipart: + maxFileSize: 10MB + maxRequestSize: 10MB + From 663dcc688860d2185084e9eeac2824782d258420 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Tue, 17 Aug 2021 17:06:30 +0900 Subject: [PATCH 39/46] =?UTF-8?q?[ADD]=20S3=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../youtubedb/YoutubeDbApplication.java | 9 ++++- .../controller/MemberController.java | 33 ++++++++++++++++--- .../exception/ControllerExceptionHandler.java | 3 +- .../exception/NotMemberException.java | 13 ++++++++ .../youtubedb/service/MemberService.java | 6 ++++ src/main/resources/application-local.yaml | 4 +-- .../service/MemberServiceIntegrationTest.java | 32 +++++++++++++++++- 8 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/exception/NotMemberException.java diff --git a/.gitignore b/.gitignore index 768fb9e..db6a36d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ application-prod.yaml +aws.yaml **/src/main/resources/application* **/src/main/resources/application-local.yaml diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index ec30892..fe13b50 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -3,13 +3,20 @@ import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableAdminServer @EnableJpaAuditing public class YoutubeDbApplication { + public static final String APPLICATION_LOCATIONS = "spring.config.location=" + + "classpath:application.yaml," + + "classpath:aws.yaml"; + public static void main(String[] args) { - SpringApplication.run(YoutubeDbApplication.class, args); + new SpringApplicationBuilder(YoutubeDbApplication.class) + .properties(APPLICATION_LOCATIONS) + .run(args); } } diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 52fa3a3..56458ac 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -17,6 +17,7 @@ import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -169,7 +171,8 @@ public ResponseEntity deleteMember(Authentication authentication) { description = "* 잘못된 요청\n" + "1. refresh 토큰 유효X\n" + "2. refresh 토큰 기간 만료\n" + - "3. refresh 토큰 불일치\n", + "3. refresh 토큰 불일치\n" + + "4. 필요값 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", description = "* 서버 에러", @@ -188,15 +191,37 @@ public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequ return ResponseEntity.ok(responseBody); } - @PostMapping("/upload") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "프로필 이미지 업로드", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 필요값 X\n" + + "2. 해당 회원이 없을 때\n" + + "3. 회원이 아닌 비회원일 때", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "* 서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "프로필 이미지 업로드", description = "프로필 이미지 업로드 & 수정") + @PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) @ResponseBody - public ResponseEntity upload(@RequestParam("img") MultipartFile img, @RequestParam("loginId") String loginId) throws IOException { + public ResponseEntity upload( + @Parameter( + description = "프로필 이미지 파일" + ) + @RequestParam("img") MultipartFile img, + @RequestPart("loginId") String loginId) throws IOException { + RequestUtil.checkNeedValue(img, loginId); + Member member = memberService.findMemberByLoginId(loginId); + memberService.checkMember(member); String profileImg = s3Uploader.upload(img, "static"); memberService.setProfileImg(member, profileImg); BaseResponseSuccessDto responseBody = new MemberResponseDto(member); - return ResponseEntity.ok(responseBody); } } diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index dc59fd1..7b79e9b 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -25,7 +25,8 @@ public class ControllerExceptionHandler { RefreshTokenException.class, InvalidBlankPasswordException.class, InvalidRegexPasswordException.class, - OverNomMemberMaxListException.class + OverNomMemberMaxListException.class, + NotMemberException.class }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/NotMemberException.java b/src/main/java/com/example/youtubedb/exception/NotMemberException.java new file mode 100644 index 0000000..6f9d40b --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/NotMemberException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class NotMemberException extends RuntimeException { + private static final String MESSAGE = "회원이 아닌 비회원입니다."; + + public NotMemberException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index cdc1968..b6a1660 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -212,4 +212,10 @@ private UserDetails createUserDetails(Member member) { public void setProfileImg(Member member, String profileImg) { member.setProfileImg(profileImg); } + + public void checkMember(Member member) { + if(!member.isMember()) { + throw new NotMemberException(); + } + } } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index f8b11d0..af518e4 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -21,6 +21,4 @@ spring: servlet: multipart: maxFileSize: 10MB - maxRequestSize: 10MB - - + maxRequestSize: 10MB \ No newline at end of file diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index 2ae2c92..aaa9fdc 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -6,13 +6,17 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -@SpringBootTest +@SpringBootTest(properties = "spring.config.location=" + + "classpath:application.yaml," + + "classpath:aws.yaml") @Transactional class MemberServiceIntegrationTest { @Autowired @@ -162,4 +166,30 @@ class MemberServiceIntegrationTest { // then assertThat(e.getMessage()).isEqualTo(DoNotMatchPasswordException.getErrorMessage()); } + + @Test + void 프로필_이미지() { + // given + String loginId = "helloMan"; + String password = "hello123**"; + String profileImg = "profile123"; + Member member = memberService.registerReal(loginId, password, false); + + // when + memberService.setProfileImg(member, profileImg); + // then + assertThat(member.getProfileImg()).isEqualTo(profileImg); + } + + @Test + void 회원X_비회원() { + // given + String loginId = "helloMan"; + Member member = memberService.registerNon(loginId, false); + + // when + Exception e = assertThrows(NotMemberException.class, () -> memberService.checkMember(member)); + // then + assertThat(e.getMessage()).isEqualTo(NotMemberException.getErrorMessage()); + } } From fdb10e77a5556286594468d21340d20217833f8d Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Wed, 18 Aug 2021 19:47:18 +0900 Subject: [PATCH 40/46] [merge] s3 --- src/main/java/com/example/youtubedb/domain/Play.java | 2 +- src/main/java/com/example/youtubedb/service/MemberService.java | 2 +- src/main/resources/application-local.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index fd42994..044dd20 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -10,7 +10,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Play extends BaseEntity { +public class Play extends BaseEntity { private String videoId; private long start; private long end; diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 6fd88cf..f0482b2 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -175,7 +175,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPC) { public Member findMemberByLoginId(String loginId) { return memberRepository.findByLoginId(loginId).orElseThrow(NotExistMemberException::new); - } + } private void checkDuplicateMember(String loginId) { memberRepository.findByLoginId(loginId).ifPresent(m -> { diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 741868c..2c24e6f 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -11,7 +11,7 @@ spring: datasource: - url: jdbc:h2:tcp://localhost/~/spring + url: jdbc:h2:tcp://localhost:9092/~/spring # url: jdbc:h2:mem:testdb username: sa password: From acda9a591c373762e19770abfb86e5c14fb2f64b Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Fri, 20 Aug 2021 00:22:01 +0900 Subject: [PATCH 41/46] =?UTF-8?q?[ADD]=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD,=20PLAY-Channel=20title=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 18 ++++--- .../youtubedb/config/jwt/TokenProvider.java | 6 +-- .../controller/MemberController.java | 44 ++++++++++++++- .../youtubedb/controller/PlayController.java | 6 ++- .../controller/PlaylistController.java | 2 +- .../com/example/youtubedb/domain/Play.java | 7 ++- .../youtubedb/domain/member/Member.java | 4 ++ .../MemberChangePasswordRequestDto.java | 22 ++++++++ .../play/request/PlayCreateRequestDto.java | 8 ++- .../exception/ControllerExceptionHandler.java | 3 +- .../DoNotChangePasswordException.java | 13 +++++ .../youtubedb/service/MemberService.java | 27 +++++++++- .../youtubedb/service/MessageService.java | 50 +++++++++++++++++ .../youtubedb/service/PlayService.java | 4 +- src/main/resources/application.yaml | 4 ++ .../service/PlayServiceIntegrationTest.java | 53 +++++++++++++------ 16 files changed, 232 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/example/youtubedb/dto/member/request/MemberChangePasswordRequestDto.java create mode 100644 src/main/java/com/example/youtubedb/exception/DoNotChangePasswordException.java create mode 100644 src/main/java/com/example/youtubedb/service/MessageService.java diff --git a/build.gradle b/build.gradle index afd73b4..adb7225 100644 --- a/build.gradle +++ b/build.gradle @@ -19,22 +19,22 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - developmentOnly 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-web:2.5.3' + developmentOnly 'org.springframework.boot:spring-boot-devtools:2.5.3' - runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.h2database:h2:1.4.200' // runtimeOnly 'mysql:mysql-connector-java' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.3' - implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-actuator:2.5.3' implementation 'de.codecentric:spring-boot-admin-starter-server:2.4.1' implementation 'de.codecentric:spring-boot-admin-starter-client:2.4.1' implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' //security - implementation "org.springframework.boot:spring-boot-starter-security" - implementation 'org.springframework.security:spring-security-test' + implementation 'org.springframework.boot:spring-boot-starter-security:2.5.3' + implementation 'org.springframework.security:spring-security-test:5.5.1' //jwt implementation 'io.jsonwebtoken:jjwt-api:0.11.2' @@ -48,6 +48,10 @@ dependencies { // aws cloud implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE' + //sms 인증 + implementation 'net.nurigo:javaSDK:2.2' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index ea1997a..e54c5b2 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -30,9 +30,9 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 ; //test용 1분 * 30 // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60; //test용 5분 * 60 * 24 * 7; // 7일 - private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60; // test용 5분 //* 60 * 24 * 7 * 4 * 3; // 3개월 + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 2; //test용 2분 * 30 // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 5; //test용 5분 * 60 * 24 * 7; // 7일 + private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 *5; // test용 5분 //* 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 56458ac..2b56070 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -5,6 +5,7 @@ import com.example.youtubedb.dto.BaseResponseSuccessDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.member.request.MemberChangePasswordRequestDto; import com.example.youtubedb.dto.member.request.MemberRequestDto; import com.example.youtubedb.dto.member.request.NonMemberRequestDto; import com.example.youtubedb.dto.member.response.MemberDeleteResponseDto; @@ -14,6 +15,7 @@ import com.example.youtubedb.dto.token.resposne.TokenResponseDto; import com.example.youtubedb.s3.S3Uploader; import com.example.youtubedb.service.MemberService; +import com.example.youtubedb.service.MessageService; import com.example.youtubedb.service.PlaylistService; import com.example.youtubedb.util.RequestUtil; import io.swagger.v3.oas.annotations.Operation; @@ -28,13 +30,13 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; // TODO actuator 관련 이슈 해결 필요! -// TODO 404에러 관리할 수 있으면 좋을듯 @Tag(name = "회원 관련 API") @RestController @@ -46,13 +48,18 @@ public class MemberController { private final PlaylistService playlistService; private final S3Uploader s3Uploader; + //test + private final MessageService messageService; + @Autowired public MemberController(MemberService memberService, PlaylistService playlistService, - S3Uploader s3Uploader) { + S3Uploader s3Uploader, + MessageService messageService) { this.memberService = memberService; this.playlistService = playlistService; this.s3Uploader = s3Uploader; + this.messageService = messageService; } @ApiResponses(value = { @@ -103,6 +110,8 @@ public ResponseEntity signupReal(@RequestBody MemberRequestDto memberRequestD memberRequestDto.getPassword(), memberRequestDto.getIsPC()); +// log.info("sms test 중, checkpoint:1"); +// messageService.sendMessage("01086231917","1234"); Member member = memberService.registerReal(memberRequestDto.getLoginId(), memberRequestDto.getPassword(), memberRequestDto.getIsPC()); playlistService.createPlaylist("default", false, "OTHER", member); @@ -191,6 +200,37 @@ public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequ return ResponseEntity.ok(responseBody); } + // TODO: 비밀번호 변경 + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "비밀 번호 변경 성공", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 비밀번호가 일치하지 않습니다.\n" + + "2. 기존 비밀번호와 같은 비밀번호 입니다.\n" + + "3. 필요값 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "* 서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "비밀 번호 변경", description = "비밀 번호 변경") + @PostMapping("/changePassword") + public ResponseEntity changePassword(@RequestBody MemberChangePasswordRequestDto memberChangePasswordRequestDto, + Authentication authentication) { + String loginId = authentication.getName(); + RequestUtil.checkNeedValue( + memberChangePasswordRequestDto.getOldPassword(), + memberChangePasswordRequestDto.getNewPassword()); + + Member updateMember = memberService.findMemberByLoginId(loginId); + updateMember = memberService.changePassword(updateMember, memberChangePasswordRequestDto.getOldPassword(),memberChangePasswordRequestDto.getNewPassword()); + + BaseResponseSuccessDto responseBody = new MemberResponseDto(updateMember); + return ResponseEntity.ok(responseBody); + } + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "프로필 이미지 업로드", diff --git a/src/main/java/com/example/youtubedb/controller/PlayController.java b/src/main/java/com/example/youtubedb/controller/PlayController.java index 1c05db1..c614b7b 100644 --- a/src/main/java/com/example/youtubedb/controller/PlayController.java +++ b/src/main/java/com/example/youtubedb/controller/PlayController.java @@ -102,7 +102,8 @@ public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto playCreate playCreateRequestDto.getEnd(), playCreateRequestDto.getThumbnail(), playCreateRequestDto.getTitle(), - playCreateRequestDto.getChannelAvatar()); + playCreateRequestDto.getChannelAvatar(), + playCreateRequestDto.getChannelTitle()); log.info(" loginId = {}", authentication.getName()); String loginId = authentication.getName(); @@ -116,7 +117,8 @@ public ResponseEntity createPlay(@RequestBody PlayCreateRequestDto playCreate playCreateRequestDto.getEnd(), playCreateRequestDto.getThumbnail(), playCreateRequestDto.getTitle(), - playCreateRequestDto.getChannelAvatar()); + playCreateRequestDto.getChannelAvatar(), + playCreateRequestDto.getChannelTitle()); BaseResponseSuccessDto responseBody = new PlayCreateResponseDto(play); diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 8c7bb53..a8ba2be 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest; import java.util.List; -// Spring Security로 JWT 적용시 변경 필요 +// TODO: 현재 중복된 플레이 리스트 이름 가능한데 논의 해봐야할듯 @Slf4j @Tag(name = "플레이 리스트 관련 API") @RestController diff --git a/src/main/java/com/example/youtubedb/domain/Play.java b/src/main/java/com/example/youtubedb/domain/Play.java index 044dd20..1f19fcf 100644 --- a/src/main/java/com/example/youtubedb/domain/Play.java +++ b/src/main/java/com/example/youtubedb/domain/Play.java @@ -10,7 +10,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Play extends BaseEntity { +public class Play extends BaseEntity { private String videoId; private long start; private long end; @@ -19,6 +19,7 @@ public class Play extends BaseEntity { @Setter private int sequence; private String channelAvatar; + private String channelTitle; @ManyToOne @JsonIgnore @JoinColumn(name = "playlist_id") @@ -31,7 +32,8 @@ public Play(String videoId, String thumbnail, String title, int sequence, - String channelAvatar) { + String channelAvatar, + String channelTitle) { this.videoId = videoId; this.start = start; this.end = end; @@ -39,6 +41,7 @@ public Play(String videoId, this.title = title; this.sequence = sequence; this.channelAvatar = channelAvatar; + this.channelTitle = channelTitle; } public void setPlaylist(Playlist playlist) { diff --git a/src/main/java/com/example/youtubedb/domain/member/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java index e26fee0..1f9b3ef 100644 --- a/src/main/java/com/example/youtubedb/domain/member/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -44,4 +44,8 @@ public Member(String loginId, String password, boolean isMember, Authority autho this.authority = authority; this.isPC = isPC; } + + public void setPassword(String password){ + this.password = password; + } } diff --git a/src/main/java/com/example/youtubedb/dto/member/request/MemberChangePasswordRequestDto.java b/src/main/java/com/example/youtubedb/dto/member/request/MemberChangePasswordRequestDto.java new file mode 100644 index 0000000..2ec48bc --- /dev/null +++ b/src/main/java/com/example/youtubedb/dto/member/request/MemberChangePasswordRequestDto.java @@ -0,0 +1,22 @@ +package com.example.youtubedb.dto.member.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +public class MemberChangePasswordRequestDto { + + @Schema(description = "기존 PASSWORD", example = "password123") + private final String oldPassword; + @Schema(description = "새로운 PASSWORD", example = "password123") + private final String newPassword; + + @Builder + public MemberChangePasswordRequestDto(String oldPassword, String newPassword){ + this.oldPassword = oldPassword; + this.newPassword = newPassword; + } + +} diff --git a/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java index 20431ff..25fb4f0 100644 --- a/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java +++ b/src/main/java/com/example/youtubedb/dto/play/request/PlayCreateRequestDto.java @@ -19,8 +19,10 @@ public class PlayCreateRequestDto { private final String thumbnail; @Schema(description = "제목" , example = "제목1") private final String title; - @Schema(description = "아바타 이미지 주소" , example = "img2") + @Schema(description = "채널 아바타 이미지 주소" , example = "img2") private final String channelAvatar; + @Schema(description = "채널 이름" , example = "채널이름1") + private final String channelTitle; @Builder public PlayCreateRequestDto( @@ -30,7 +32,8 @@ public PlayCreateRequestDto( Long end, String thumbnail, String title, - String channelAvatar) { + String channelAvatar, + String channelTitle) { this.playlistId = playlistId; this.videoId = videoId; this.start = start; @@ -38,5 +41,6 @@ public PlayCreateRequestDto( this.thumbnail = thumbnail; this.title = title; this.channelAvatar = channelAvatar; + this.channelTitle =channelTitle; } } diff --git a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java index 7b79e9b..d49e346 100644 --- a/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java +++ b/src/main/java/com/example/youtubedb/exception/ControllerExceptionHandler.java @@ -26,7 +26,8 @@ public class ControllerExceptionHandler { InvalidBlankPasswordException.class, InvalidRegexPasswordException.class, OverNomMemberMaxListException.class, - NotMemberException.class + NotMemberException.class, + DoNotChangePasswordException.class }) public ResponseEntity badRequest(Exception e) { BaseResponseFailDto responseBody = BadRequestFailResponseDto.builder() diff --git a/src/main/java/com/example/youtubedb/exception/DoNotChangePasswordException.java b/src/main/java/com/example/youtubedb/exception/DoNotChangePasswordException.java new file mode 100644 index 0000000..6b67b19 --- /dev/null +++ b/src/main/java/com/example/youtubedb/exception/DoNotChangePasswordException.java @@ -0,0 +1,13 @@ +package com.example.youtubedb.exception; + +public class DoNotChangePasswordException extends RuntimeException { + private static final String MESSAGE = "기존 비밀번호와 같은 비밀번호 입니다."; + + public DoNotChangePasswordException() { + super(MESSAGE); + } + + public static String getErrorMessage() { + return MESSAGE; + } +} diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 246db16..8b60dd9 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -17,6 +17,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -27,6 +30,7 @@ import java.util.Collections; import java.util.Date; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -86,6 +90,27 @@ public Member registerReal(String loginId, String password, Boolean isPC) { return memberRepository.save(realMember); } + public Member changePassword(Member updateMember, String oldPassword, String newPassword) { + checkCorrectPassword(updateMember, oldPassword, newPassword); + checkValidPassword(newPassword); + + updateMember.setPassword(passwordEncoder.encode(newPassword)); + template.delete("PC" + updateMember.getLoginId()); + template.delete("APP" + updateMember.getLoginId()); + + return memberRepository.save(updateMember); + } + + private void checkCorrectPassword(Member updateMember, String oldPassword, String newPassword) { + if(!passwordEncoder.matches(oldPassword, updateMember.getPassword())){ + throw new DoNotMatchPasswordException(); + } + if (oldPassword.equals(newPassword)) { + throw new DoNotChangePasswordException(); + } + } + + private void checkValidPassword(String password) { int min = 8; int max = 20; @@ -168,7 +193,7 @@ public Token reissue(String accessToken, String refreshToken, boolean isPC) { // 5. 새로운 토큰 생성 Token tokenDto = tokenProvider.generateTokenDto(authentication, isPC); tokenDto.setRefreshToken(redisRefreshToken); - + // 토큰 발급 return tokenDto; } diff --git a/src/main/java/com/example/youtubedb/service/MessageService.java b/src/main/java/com/example/youtubedb/service/MessageService.java new file mode 100644 index 0000000..646d4df --- /dev/null +++ b/src/main/java/com/example/youtubedb/service/MessageService.java @@ -0,0 +1,50 @@ +package com.example.youtubedb.service; + +import lombok.extern.slf4j.Slf4j; +import net.nurigo.java_sdk.api.Message; +import net.nurigo.java_sdk.exceptions.CoolsmsException; +import org.json.simple.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +@Slf4j +@Component +public class MessageService { + +// @Value("${coolsms.devHee.apikey}") + private String apiKey = "NCSPKDQXASHQOBBK"; + +// @Value("${coolsms.devHee.apisecret}") + private String apiSecret = "DDE4POPKWR09KGRFYYKJYCVUVI99PNQ7"; + +// @Value("${coolsms.devHee.fromnumber}") + private String fromNumber = "01086231917"; + + public void sendMessage(String toNumber, String randomNumber) { + + log.info("test중 1"); + Message coolsms = new Message(apiKey, apiSecret); + + log.info("test중 2"); + HashMap params = new HashMap(); + params.put("to", toNumber); + params.put("from", fromNumber); + params.put("type", "SMS"); + params.put("text", "문자가면 연락좀"); + params.put("kakaoOptions", null); +// "[grabMe] 인증번호 "+randomNumber+" 를 입력하세요."); + params.put("app_version", "test app 1.2"); // application name and version + log.info("test중 3"); + + try { + JSONObject obj = (JSONObject) coolsms.send(params); + System.out.println(obj.toString()); + } catch (CoolsmsException e) { + System.out.println(e.getMessage()); + System.out.println(e.getCode()); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/youtubedb/service/PlayService.java b/src/main/java/com/example/youtubedb/service/PlayService.java index 16e3f02..54181a8 100644 --- a/src/main/java/com/example/youtubedb/service/PlayService.java +++ b/src/main/java/com/example/youtubedb/service/PlayService.java @@ -32,7 +32,8 @@ public Play addPlayToPlaylist( Long end, String thumbnail, String title, - String channelAvatar) { + String channelAvatar, + String channelTitle) { checkTime(start, end); RequestUtil.checkOwn(playlist.getMember().getLoginId(), loginId); @@ -46,6 +47,7 @@ public Play addPlayToPlaylist( .start(start) .end(end) .channelAvatar(channelAvatar) + .channelTitle(channelTitle) .build(); play.setPlaylist(playlist); playRepository.save(play); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d93f023..3be5e4c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -36,6 +36,10 @@ logging: jwt: secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXR1dG9yaWFsLWppd29vbi1zcHJpbmctYm9vdC1zZWN1cml0eS1qd3QtdHV0b3JpYWwK +coolsms: + devHee: + apiKey: NCSVJBMVYB7TOOQV + management: endpoints: web: diff --git a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java index 37b4f4c..8aea625 100644 --- a/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/PlayServiceIntegrationTest.java @@ -34,6 +34,7 @@ class PlayServiceIntegrationTest { private String thumbnail; private String title; private String channelAvatar; + private String channelTitle; private boolean isPc; @BeforeEach @@ -45,6 +46,7 @@ void setup() { this.thumbnail = "썸네일"; this.title = "영상1"; this.channelAvatar = "아바타 이미지"; + this.channelTitle ="채널이름1"; } @Test @@ -62,7 +64,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); // then assertAll( @@ -73,7 +76,8 @@ void setup() { () -> assertThat(play.getThumbnail()).isEqualTo(thumbnail), () -> assertThat(play.getSequence()).isEqualTo(1), () -> assertThat(play.getChannelAvatar()).isEqualTo(channelAvatar), - () -> assertThat(play.getPlaylist().getId()).isEqualTo(playlist.getId()) + () -> assertThat(play.getPlaylist().getId()).isEqualTo(playlist.getId()), + () -> assertThat(play.getChannelTitle()).isEqualTo(channelTitle) ); } @@ -93,7 +97,8 @@ void setup() { -5L, thumbnail, title, - channelAvatar) + channelAvatar, + channelTitle) ); // then @@ -113,7 +118,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); // when List plays = playService.getPlaysInPlaylist(playlist, member.getLoginId()); @@ -161,7 +167,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); // when playService.editTime(play, member.getLoginId(), 50L, 100L); @@ -187,7 +194,8 @@ void setup() { end, thumbnail, "play1", - channelAvatar); + channelAvatar, + channelTitle); Play play2 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -196,7 +204,8 @@ void setup() { end, thumbnail, "play2", - channelAvatar); + channelAvatar, + channelTitle); Play play3 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -205,7 +214,8 @@ void setup() { end, thumbnail, "play3", - channelAvatar); + channelAvatar, + channelTitle); List seqList = new ArrayList<>(); seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(3).build()); seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(1).build()); @@ -235,7 +245,8 @@ void setup() { end, thumbnail, "play1", - channelAvatar); + channelAvatar, + channelTitle); Play play2 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -244,7 +255,8 @@ void setup() { end, thumbnail, "play2", - channelAvatar); + channelAvatar, + channelTitle); Play play3 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -253,7 +265,8 @@ void setup() { end, thumbnail, "play3", - channelAvatar); + channelAvatar, + channelTitle); List seqList = new ArrayList<>(); seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(3).build()); seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(4).build()); @@ -279,7 +292,8 @@ void setup() { end, thumbnail, "play1", - channelAvatar); + channelAvatar, + channelTitle); Play play2 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -288,7 +302,8 @@ void setup() { end, thumbnail, "play2", - channelAvatar); + channelAvatar, + channelTitle); Play play3 = playService.addPlayToPlaylist( playlist, member.getLoginId(), @@ -297,7 +312,8 @@ void setup() { end, thumbnail, "play3", - channelAvatar); + channelAvatar, + channelTitle); List seqList = new ArrayList<>(); seqList.add(PlaySeqDto.builder().id(play1.getId()).sequence(1).build()); seqList.add(PlaySeqDto.builder().id(play2.getId()).sequence(2).build()); @@ -323,7 +339,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); // when playService.deletePlayById(play, "device001"); @@ -346,7 +363,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); Play play2 = playService.addPlayToPlaylist( playlist, @@ -356,7 +374,8 @@ void setup() { end, thumbnail, title, - channelAvatar); + channelAvatar, + channelTitle); play2.setSequence(3); // Play play3 = playService.addPlayToPlaylist( // playlist, From 90ab0918d464919f5bcafff08553c8f2f85db606 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Fri, 20 Aug 2021 11:47:49 +0900 Subject: [PATCH 42/46] =?UTF-8?q?[ADD]=20=ED=9A=8C=EC=9B=90=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EC=A1=B0=ED=9A=8C=20+=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/config/WebSecurityConfig.java | 2 + .../controller/MemberController.java | 44 ++++++++++++++++--- .../controller/PlaylistController.java | 3 -- .../service/MemberServiceIntegrationTest.java | 2 - 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 02b7172..4f9bb4c 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -44,6 +44,8 @@ protected void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .antMatchers("/api/member").authenticated() + .antMatchers("/api/member/upload").authenticated() .antMatchers("/api/member/**").permitAll() .antMatchers("/api/**").authenticated() // .antMatchers("/swagger-ui/**").hasRole("ADMIN") diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index 56458ac..cd49238 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -3,6 +3,7 @@ import com.example.youtubedb.domain.Token; import com.example.youtubedb.domain.member.Member; import com.example.youtubedb.dto.BaseResponseSuccessDto; +import com.example.youtubedb.dto.error.AuthenticationEntryPointFailResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; import com.example.youtubedb.dto.member.request.MemberRequestDto; @@ -55,6 +56,33 @@ public MemberController(MemberService memberService, this.s3Uploader = s3Uploader; } + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 아이디 존재 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "401", + description = "인증되지 않음", + content = @Content(schema = @Schema(implementation = AuthenticationEntryPointFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "조회", description = "회원&비회원 정보 조회") + @GetMapping + @ResponseBody + public ResponseEntity getMemberInfo(Authentication authentication) { + log.info(" loginId = {}", authentication.getName()); + String loginId = authentication.getName(); + Member member = memberService.findMemberByLoginId(loginId); + + BaseResponseSuccessDto responseBody = new MemberResponseDto(member); + return ResponseEntity.ok(responseBody); + } + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "비회원 생성 성공", @@ -197,10 +225,13 @@ public ResponseEntity reissue(@RequestBody TokenReissueRequestDto reissueRequ content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), @ApiResponse(responseCode = "400", description = "* 잘못된 요청\n" + - "1. 필요값 X\n" + - "2. 해당 회원이 없을 때\n" + - "3. 회원이 아닌 비회원일 때", + "1. 아이디 존재 X\n" + + "2. 회원이 아닌 비회원일 때\n" + + "3. 필요값 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "401", + description = "인증되지 않음", + content = @Content(schema = @Schema(implementation = AuthenticationEntryPointFailResponseDto.class))), @ApiResponse(responseCode = "500", description = "* 서버 에러", content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) @@ -213,9 +244,10 @@ public ResponseEntity upload( description = "프로필 이미지 파일" ) @RequestParam("img") MultipartFile img, - @RequestPart("loginId") String loginId) throws IOException { - RequestUtil.checkNeedValue(img, loginId); - + Authentication authentication) throws IOException { + RequestUtil.checkNeedValue(img); + log.info(" loginId = {}", authentication.getName()); + String loginId = authentication.getName(); Member member = memberService.findMemberByLoginId(loginId); memberService.checkMember(member); String profileImg = s3Uploader.upload(img, "static"); diff --git a/src/main/java/com/example/youtubedb/controller/PlaylistController.java b/src/main/java/com/example/youtubedb/controller/PlaylistController.java index 8c7bb53..731e4e3 100644 --- a/src/main/java/com/example/youtubedb/controller/PlaylistController.java +++ b/src/main/java/com/example/youtubedb/controller/PlaylistController.java @@ -65,10 +65,7 @@ public PlaylistController(PlaylistService playlistService, public ResponseEntity getPlaylist(Authentication authentication) { log.info(" loginId = {}", authentication.getName()); String loginId = authentication.getName(); - - Member member = memberService.findMemberByLoginId(loginId); - List playlists = member.getPlaylists(); playlistService.addThumbnail(playlists); diff --git a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java index aaa9fdc..b47b1be 100644 --- a/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/example/youtubedb/service/MemberServiceIntegrationTest.java @@ -6,8 +6,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; From 740a2121dbb9e67e68680547aa83fb282c2d30b5 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Fri, 20 Aug 2021 15:00:32 +0900 Subject: [PATCH 43/46] =?UTF-8?q?[ADD]=20=EB=B9=84=ED=9A=8C=EC=9B=90->?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EB=B3=80=EA=B2=BD=20&=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EB=9E=9C=EB=8D=A4=20&=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtubedb/config/WebSecurityConfig.java | 1 + .../controller/MemberController.java | 36 ++++++++++++++++++- .../youtubedb/domain/member/Member.java | 6 ++++ .../com/example/youtubedb/s3/S3Uploader.java | 7 ++-- .../youtubedb/service/MemberService.java | 5 +++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java index 4f9bb4c..4676876 100644 --- a/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java +++ b/src/main/java/com/example/youtubedb/config/WebSecurityConfig.java @@ -45,6 +45,7 @@ protected void configure(HttpSecurity http) throws Exception { .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .antMatchers("/api/member").authenticated() + .antMatchers("/api/member/change").authenticated() .antMatchers("/api/member/upload").authenticated() .antMatchers("/api/member/**").permitAll() .antMatchers("/api/**").authenticated() diff --git a/src/main/java/com/example/youtubedb/controller/MemberController.java b/src/main/java/com/example/youtubedb/controller/MemberController.java index cd49238..d374834 100644 --- a/src/main/java/com/example/youtubedb/controller/MemberController.java +++ b/src/main/java/com/example/youtubedb/controller/MemberController.java @@ -6,6 +6,7 @@ import com.example.youtubedb.dto.error.AuthenticationEntryPointFailResponseDto; import com.example.youtubedb.dto.error.BadRequestFailResponseDto; import com.example.youtubedb.dto.error.ServerErrorFailResponseDto; +import com.example.youtubedb.dto.member.request.MemberLoginRequestDto; import com.example.youtubedb.dto.member.request.MemberRequestDto; import com.example.youtubedb.dto.member.request.NonMemberRequestDto; import com.example.youtubedb.dto.member.response.MemberDeleteResponseDto; @@ -117,7 +118,8 @@ public ResponseEntity signupNon(@RequestBody NonMemberRequestDto nonMemberReq @ApiResponse(responseCode = "400", description = "* 잘못된 요청\n" + "1. 중복된 아이디 존재\n" + - "2. 필요값 X", + "2. 필요값 X\n" + + "3. 비밀번호 생성규칙 X", content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), @ApiResponse(responseCode = "500", description = "* 서버 에러", @@ -256,4 +258,36 @@ public ResponseEntity upload( BaseResponseSuccessDto responseBody = new MemberResponseDto(member); return ResponseEntity.ok(responseBody); } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "회원으로 변경", + content = @Content(schema = @Schema(implementation = MemberResponseDto.class))), + @ApiResponse(responseCode = "400", + description = "* 잘못된 요청\n" + + "1. 아이디 존재 X\n" + + "2. 필요값 X\n" + + "3. 비밀번호 생성규칙 X", + content = @Content(schema = @Schema(implementation = BadRequestFailResponseDto.class))), + @ApiResponse(responseCode = "401", + description = "인증되지 않음", + content = @Content(schema = @Schema(implementation = AuthenticationEntryPointFailResponseDto.class))), + @ApiResponse(responseCode = "500", + description = "서버 에러", + content = @Content(schema = @Schema(implementation = ServerErrorFailResponseDto.class))) + }) + @Operation(summary = "회원으로 변경", description = "비회원에서 회원으로 변경") + @PutMapping("/change") + @ResponseBody + public ResponseEntity changeToMember(Authentication authentication, + @RequestBody MemberLoginRequestDto memberLoginRequestDto) { + RequestUtil.checkNeedValue(memberLoginRequestDto.getLoginId(), memberLoginRequestDto.getPassword()); + log.info(" loginId = {}", authentication.getName()); + String loginId = authentication.getName(); + Member member = memberService.findMemberByLoginId(loginId); + memberService.change(member, memberLoginRequestDto.getLoginId(), memberLoginRequestDto.getPassword()); + + BaseResponseSuccessDto responseBody = new MemberResponseDto(member); + return ResponseEntity.ok(responseBody); + } } diff --git a/src/main/java/com/example/youtubedb/domain/member/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java index e26fee0..2ae8e87 100644 --- a/src/main/java/com/example/youtubedb/domain/member/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -44,4 +44,10 @@ public Member(String loginId, String password, boolean isMember, Authority autho this.authority = authority; this.isPC = isPC; } + + public void changeToMember(String loginId, String password) { + this.loginId = loginId; + this.password = password; + this.isMember = true; + } } diff --git a/src/main/java/com/example/youtubedb/s3/S3Uploader.java b/src/main/java/com/example/youtubedb/s3/S3Uploader.java index 34ae91e..f500977 100644 --- a/src/main/java/com/example/youtubedb/s3/S3Uploader.java +++ b/src/main/java/com/example/youtubedb/s3/S3Uploader.java @@ -50,8 +50,11 @@ private void removeNewFile(File targetFile) { } private Optional convert(MultipartFile file) throws IOException { - System.out.println(file.getOriginalFilename()); - File convertFile = new File(file.getOriginalFilename()); + String fileName = file.getOriginalFilename(); + log.info("Long time: {}", System.currentTimeMillis()); + String newFileName = String.valueOf(System.currentTimeMillis()) + String.valueOf((int) (Math.random()*1000)) + fileName.substring(fileName.lastIndexOf(".")); + log.info("newFileName: {}", newFileName); + File convertFile = new File(newFileName); if(convertFile.createNewFile()) { try (FileOutputStream fos = new FileOutputStream(convertFile)) { fos.write(file.getBytes()); diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index b6a1660..7840413 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -218,4 +218,9 @@ public void checkMember(Member member) { throw new NotMemberException(); } } + + public void change(Member member, String loginId, String password) { + checkValidPassword(password); + member.changeToMember(loginId, passwordEncoder.encode(password)); + } } From 1705ad2965c72f03d01a7f8179352ae381dbd61b Mon Sep 17 00:00:00 2001 From: oh980225 Date: Fri, 20 Aug 2021 17:09:46 +0900 Subject: [PATCH 44/46] =?UTF-8?q?[FIX]=20=EC=A4=91=EB=B3=B5=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/youtubedb/service/MemberService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/example/youtubedb/service/MemberService.java b/src/main/java/com/example/youtubedb/service/MemberService.java index 7840413..1b317d1 100644 --- a/src/main/java/com/example/youtubedb/service/MemberService.java +++ b/src/main/java/com/example/youtubedb/service/MemberService.java @@ -220,6 +220,7 @@ public void checkMember(Member member) { } public void change(Member member, String loginId, String password) { + checkDuplicateMember(loginId); checkValidPassword(password); member.changeToMember(loginId, passwordEncoder.encode(password)); } From 5b8377b3c6350623c90365523b970bdf86835255 Mon Sep 17 00:00:00 2001 From: JisooPark27 Date: Sun, 22 Aug 2021 22:55:14 +0900 Subject: [PATCH 45/46] =?UTF-8?q?[FIX]=20=EB=B9=84=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=ED=95=9C,=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=EC=8B=9C=EA=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/youtubedb/config/jwt/TokenProvider.java | 6 +++--- .../java/com/example/youtubedb/service/PlaylistService.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java index e54c5b2..b70a500 100644 --- a/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java +++ b/src/main/java/com/example/youtubedb/config/jwt/TokenProvider.java @@ -30,9 +30,9 @@ public class TokenProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String BEARER_TYPE = "bearer"; - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 2; //test용 2분 * 30 // 30분 - private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 5; //test용 5분 * 60 * 24 * 7; // 7일 - private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 *5; // test용 5분 //* 60 * 24 * 7 * 4 * 3; // 3개월 + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 2 * 30; // 30분 + private static final long REFRESH_TOKEN_EXPIRE_TIME_APP = 1000L * 60 * 60 * 24 * 7; // 7일 + private static final long REFRESH_TOKEN_EXPIRE_TIME_PC = 1000L * 60 * 60 * 24 * 7 * 4 * 3; // 3개월 private final Key key; diff --git a/src/main/java/com/example/youtubedb/service/PlaylistService.java b/src/main/java/com/example/youtubedb/service/PlaylistService.java index 7cbcbca..0a66a68 100644 --- a/src/main/java/com/example/youtubedb/service/PlaylistService.java +++ b/src/main/java/com/example/youtubedb/service/PlaylistService.java @@ -23,7 +23,7 @@ public class PlaylistService { private final PlaylistRepository playlistRepository; - private final int NON_MEMBER_MAX_PLAYLISTS = 10; + private final int NON_MEMBER_MAX_PLAYLISTS = 5; @Autowired public PlaylistService(PlaylistRepository playlistRepository) { From 46a3c48b2470738e5f93f2a3c488f292bffc7168 Mon Sep 17 00:00:00 2001 From: oh980225 Date: Mon, 23 Aug 2021 20:20:29 +0900 Subject: [PATCH 46/46] =?UTF-8?q?[ADD]=20Actuator=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 - .../com/example/youtubedb/YoutubeDbApplication.java | 3 --- .../com/example/youtubedb/domain/member/Member.java | 1 + src/main/resources/application.yaml | 10 +++++----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index afd73b4..7045f69 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'de.codecentric:spring-boot-admin-starter-server:2.4.1' implementation 'de.codecentric:spring-boot-admin-starter-client:2.4.1' implementation 'org.springdoc:springdoc-openapi-ui:1.5.7' diff --git a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java index fe13b50..cecde24 100644 --- a/src/main/java/com/example/youtubedb/YoutubeDbApplication.java +++ b/src/main/java/com/example/youtubedb/YoutubeDbApplication.java @@ -1,13 +1,10 @@ package com.example.youtubedb; -import de.codecentric.boot.admin.server.config.EnableAdminServer; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication -@EnableAdminServer @EnableJpaAuditing public class YoutubeDbApplication { public static final String APPLICATION_LOCATIONS = "spring.config.location=" diff --git a/src/main/java/com/example/youtubedb/domain/member/Member.java b/src/main/java/com/example/youtubedb/domain/member/Member.java index 2ae8e87..31dc495 100644 --- a/src/main/java/com/example/youtubedb/domain/member/Member.java +++ b/src/main/java/com/example/youtubedb/domain/member/Member.java @@ -29,6 +29,7 @@ public class Member extends BaseEntity { @Enumerated(value = EnumType.STRING) private Authority authority; + @JsonIgnore @Transient private boolean isPC; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d93f023..1fdc7ed 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -12,11 +12,10 @@ spring: # password: "pass" # roles: "ADMIN" - boot: admin: client: - url: http://localhost:8080 + url: http://localhost:8090 properties: hibernate: @@ -37,17 +36,18 @@ jwt: secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXR1dG9yaWFsLWppd29vbi1zcHJpbmctYm9vdC1zZWN1cml0eS1qd3QtdHV0b3JpYWwK management: + server: + port: 8001 endpoints: web: exposure: include: "*" - base-path: "/monitoring" endpoint: health: group: custom: - include: diskSpace, ping + include: "*" show-components: always - show-details: always + show-details: always \ No newline at end of file