-
Notifications
You must be signed in to change notification settings - Fork 81
[그리디] 서현진 Spring JPA (1차) 4,5,6 단계 미션 제출합니다. #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: nonactress
Are you sure you want to change the base?
Changes from all commits
29a3411
76e20ba
6884888
b994a63
9234df4
103e6bb
ecd0c7f
334327e
eeb4f66
a75257b
f95bdc5
c1610e8
aa8870e
9f54ac5
a674fa5
f9f63d1
5ee3e74
cc28fde
b5cca3b
9deeb22
7742db7
0812bd7
8f084a7
6d27e4c
3a50097
4178346
dcdd017
c7a6a64
4252f96
e948375
bb539bd
c9f6c0d
841a720
47e66f3
941299b
42fe9b0
658a29b
d10537a
e13ba3b
34debcb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package roomescape.advice; | ||
|
|
||
| public record ErrorResponse(String message) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package roomescape.advice; | ||
|
|
||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
|
|
||
| @ControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) { | ||
| return ResponseEntity.badRequest() | ||
| .body(new ErrorResponse(e.getMessage())); | ||
| } | ||
|
|
||
|
|
||
| @ExceptionHandler(NotFoundException.class) | ||
| public ResponseEntity<ErrorResponse> handleNotFoundException(RuntimeException e) { | ||
| return ResponseEntity.status(404) | ||
| .body(new ErrorResponse(e.getMessage())); | ||
| } | ||
|
|
||
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||
| return ResponseEntity.internalServerError() | ||
| .body(new ErrorResponse("오류가 발생했습니다.")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package roomescape.advice; | ||
|
|
||
| public class NotFoundException extends RuntimeException { | ||
| public NotFoundException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package roomescape.auth; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target(ElementType.PARAMETER) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface AuthMember { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package roomescape.infrastructure; | ||
|
|
||
| import org.springframework.boot.CommandLineRunner; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import roomescape.member.Member; | ||
| import roomescape.member.MemberRepository; | ||
| import roomescape.reservation.Reservation; | ||
| import roomescape.reservation.ReservationRepository; | ||
| import roomescape.theme.Theme; | ||
| import roomescape.theme.ThemeRepository; | ||
| import roomescape.time.Time; | ||
| import roomescape.time.TimeRepository; | ||
|
|
||
| @Component | ||
| public class DataInitializer implements CommandLineRunner { | ||
|
|
||
| private final MemberRepository memberRepository; | ||
| private final ThemeRepository themeRepository; | ||
| private final TimeRepository timeRepository; | ||
| private final ReservationRepository reservationRepository; | ||
| private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataInitializer.class); | ||
|
|
||
| public DataInitializer( | ||
| MemberRepository memberRepository, | ||
| ThemeRepository themeRepository, | ||
| TimeRepository timeRepository, | ||
| ReservationRepository reservationRepository | ||
| ) { | ||
| this.memberRepository = memberRepository; | ||
| this.themeRepository = themeRepository; | ||
| this.timeRepository = timeRepository; | ||
| this.reservationRepository = reservationRepository; | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void run(String... args) throws Exception { | ||
| // 1. 관리자 계정 생성 | ||
| if (memberRepository.findByEmail("admin").isEmpty()) { | ||
| Member admin = new Member("admin", "admin", "admin", "ADMIN"); | ||
| Member user = new Member("user", "user", "user", "USER"); | ||
| memberRepository.save(admin); | ||
| memberRepository.save(user); | ||
| log.info("관리자 계정이 생성되었습니다."); | ||
| } | ||
|
|
||
| // 2. 테마 데이터 생성 | ||
| if (themeRepository.count() == 0) { | ||
| Theme theme1 = new Theme("테마1", "테마1입니다."); | ||
| Theme theme2 = new Theme("테마2", "테마2입니다."); | ||
| Theme theme3 = new Theme("테마3", "테마3입니다."); | ||
|
|
||
| themeRepository.save(theme1); | ||
| themeRepository.save(theme2); | ||
| themeRepository.save(theme3); | ||
| log.info("테마 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| // 3. 시간 데이터 생성 | ||
| if (timeRepository.count() == 0) { | ||
| Time time1 = new Time("10:00"); | ||
| Time time2 = new Time("12:00"); | ||
| Time time3 = new Time("14:00"); | ||
| Time time4 = new Time("16:00"); | ||
| Time time5 = new Time("18:00"); | ||
| Time time6 = new Time("20:00"); | ||
|
|
||
| timeRepository.save(time1); | ||
| timeRepository.save(time2); | ||
| timeRepository.save(time3); | ||
| timeRepository.save(time4); | ||
| timeRepository.save(time5); | ||
| timeRepository.save(time6); | ||
| log.info("시간 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| if (reservationRepository.count() == 0) { | ||
| Member admin = memberRepository.findByEmail("admin") | ||
| .orElseThrow(() -> new RuntimeException("Admin not found")); | ||
|
|
||
| Time time1 = timeRepository.findById(1L).orElseThrow(); | ||
| Time time2 = timeRepository.findById(2L).orElseThrow(); | ||
| Time time3 = timeRepository.findById(3L).orElseThrow(); | ||
|
|
||
| Theme theme1 = themeRepository.findById(1L).orElseThrow(); | ||
| Theme theme2 = themeRepository.findById(2L).orElseThrow(); | ||
| Theme theme3 = themeRepository.findById(3L).orElseThrow(); | ||
|
|
||
| Reservation reservation1 = new Reservation("", "2024-03-01", time1, theme1, admin); | ||
| Reservation reservation2 = new Reservation("", "2024-03-01", time2, theme2, admin); | ||
| Reservation reservation3 = new Reservation("", "2024-03-01", time3, theme3, admin); | ||
|
|
||
| reservationRepository.save(reservation1); | ||
| reservationRepository.save(reservation2); | ||
| reservationRepository.save(reservation3); | ||
|
|
||
| Reservation reservation4 = new Reservation("브라운", "2024-03-01", time1, theme2); | ||
|
|
||
| reservationRepository.save(reservation4); | ||
| log.info("예약 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| log.info("초기 데이터 로딩이 완료되었습니다."); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package roomescape.infrastructure; | ||
|
|
||
| import io.jsonwebtoken.Claims; | ||
| import io.jsonwebtoken.Jws; | ||
| import io.jsonwebtoken.JwtException; | ||
| import io.jsonwebtoken.Jwts; | ||
| import io.jsonwebtoken.SignatureAlgorithm; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
| @Component | ||
| public class JwtTokenProvider { | ||
| @Value("${security.jwt.token.secret-key:this-is-a-sample-secret-key-at-least-32-bytes-long}") | ||
| private String secretKey; | ||
| @Value("${security.jwt.token.expire-length:3600000}") | ||
| private long validityInMilliseconds; | ||
|
|
||
| public String createToken(String payload) { | ||
| Claims claims = Jwts.claims().setSubject(payload); | ||
| Date now = new Date(); | ||
| Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
|
|
||
| return Jwts.builder() | ||
| .setClaims(claims) | ||
| .setIssuedAt(now) | ||
| .setExpiration(validity) | ||
| .signWith(SignatureAlgorithm.HS256, secretKey) | ||
| .compact(); | ||
| } | ||
|
|
||
| public String getPayload(String token) { | ||
| return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); | ||
| } | ||
|
|
||
| public boolean validateToken(String token) { | ||
| try { | ||
| Jws<Claims> claims = Jwts.parser() | ||
| .setSigningKey(secretKey) | ||
| .parseClaimsJws(token); | ||
|
|
||
| return !claims.getBody() | ||
| .getExpiration() | ||
| .before(new Date()); | ||
|
|
||
| } catch (JwtException | IllegalArgumentException e) { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,37 @@ | ||
| package roomescape.member; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
|
|
||
| @Entity | ||
| @Table(name = "member") | ||
| public class Member { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(nullable = false) | ||
| private String name; | ||
|
|
||
| @Column(nullable = false, unique = true) | ||
| private String email; | ||
|
|
||
| @Column(nullable = false) | ||
| private String password; | ||
|
|
||
| @Column(nullable = false) | ||
| private String role; | ||
|
|
||
| @Column(name = "deleted") | ||
| private boolean deleted = false; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delete 종류도 soft delete, hard delete가 있는데, 현진님이 soft delete를 선택한 이유는 무엇인가요?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 member가 삭제되더라도 reservation 값들을 가지고 있어야 하는데 만약 hard delete를 이용하면 member가 없어짐에 따라reservation에 있는 member_fk가 가르키는 값이 없어지게 될 수 있으므로 soft delete를 사용했습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그러면 reservation 데이터도 같이 없어지면 되는거 아닌가라는 생각이 드네요 제가 같이 없어지면 되는게 아닌가라고 생각하는 이유 |
||
|
|
||
| protected Member() { | ||
| } | ||
|
|
||
| public Member(Long id, String name, String email, String role) { | ||
| this.id = id; | ||
| this.name = name; | ||
|
|
@@ -40,4 +65,8 @@ public String getPassword() { | |
| public String getRole() { | ||
| return role; | ||
| } | ||
|
|
||
| public void delete() { | ||
| this.deleted = true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,14 @@ | ||
| package roomescape.member; | ||
|
|
||
| import jakarta.servlet.http.Cookie; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.http.HttpStatus; | ||
| 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; | ||
| import roomescape.auth.AuthMember; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
|
|
@@ -19,6 +20,39 @@ public MemberController(MemberService memberService) { | |
| this.memberService = memberService; | ||
| } | ||
|
|
||
| @PostMapping("/login") | ||
| public ResponseEntity<Void> login( | ||
| @RequestBody MemberRequest memberRequest, | ||
| HttpServletResponse response | ||
| ) { | ||
|
|
||
| String tokenValue = memberService.login(memberRequest); | ||
|
|
||
| Cookie cookie = new Cookie("token", tokenValue); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(3600); | ||
| response.addCookie(cookie); | ||
|
|
||
| return ResponseEntity.ok() | ||
| .header("Keep-Alive", "timeout=60") | ||
| .build(); | ||
| } | ||
|
|
||
| @GetMapping("/login/check") | ||
| public ResponseEntity<MemberResponse> check( | ||
| @AuthMember Member member | ||
| ) { | ||
| if (member == null) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); | ||
| } | ||
|
|
||
| return ResponseEntity.ok() | ||
| .header("Connection", "keep-alive") | ||
| .header("Keep-Alive", "timeout=60") | ||
|
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기 궁금한 부분이 있는데요
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .body(new MemberResponse(null, member.getName(), null)); | ||
| } | ||
|
|
||
| @PostMapping("/members") | ||
| public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { | ||
| MemberResponse member = memberService.createMember(memberRequest); | ||
|
|
||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기만 이름을 명시해둔 이유가 있나요?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
따로 이유는 없고 다른 필드들 nullable처리를 해서 이름도 이렇게 설정하는 구나 해서 넣어봤습니다!