-
Notifications
You must be signed in to change notification settings - Fork 82
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
[1 - 2단계 방탈출 예약 대기] 이든(최승준) 미션 제출합니다. #86
Changes from 21 commits
39ebbcb
a8e288f
c6d87a2
b0fca6a
ec79bb0
75da8ed
dcb7d4f
f35ff04
78e632c
a790f9e
92bc885
428054a
68f8d7a
e6c1d0a
47a0914
fb4d90c
4de3664
a0bfd63
3da51a3
8c5ea0d
79a70d9
177c6cf
84cbb98
5accf81
f2209cd
db74b5c
9ac7445
a7b7a64
d17de2f
f0786d6
c611311
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 |
---|---|---|
|
@@ -6,16 +6,21 @@ | |
|----------|--------|---------------------------------------------------------|-----------------------|----------------------------------------|-------------------| | ||
| | GET | `/` | 인기 테마 페이지 요청 | `templates/index.html` | `@Controller` | | ||
| | GET | `/reservation` | 사용자 예약 페이지 요청 | `templates/reservation.html` | `@Controller` | | ||
| | GET | `/reservation-mine` | 내 예약 조회 페이지 요청 | `templates/reservation-mine.html` | `@Controller` | | ||
| `ADMIN` | GET | `/admin` | 어드민 페이지 요청 | `templates/admin/index.html` | `@Controller` | | ||
| `ADMIN` | GET | `/admin/reservation` | 예약 관리 페이지 요청 | `templates/admin/reservation-new.html` | `@Controller` | | ||
| `ADMIN` | GET | `/admin/reservationTime` | 예약 시간 관리 페이지 요청 | `templates/admin/reservationTime.html` | `@Controller` | | ||
| `ADMIN` | GET | `/admin/theme` | 테마 관리 페이지 요청 | `templates/admin/theme.html` | `@Controller` | | ||
| | GET | `/login` | 로그인 페이지 요청 | `templates/login.html` | `@Controller` | | ||
| | GET | `/signup` | 회원가입 페이지 요청 | | `@Controller` | | ||
| | POST | `/login` | 로그인 요청 | | `@RestController` | | ||
| | POST | `/signup` | 회원가입 요청 | | `@RestController` | | ||
| | POST | `/logout` | 로그아웃 요청 | | `@RestController` | | ||
| | GET | `/login/check` | 인증 정보 조회 | | `@RestController` | | ||
| | GET | `/token-reissue` | JWT 토큰 재발급 | | `@RestController` | | ||
| | GET | `/reservations` | 예약 정보 조회 | | `@RestController` | | ||
| | GET | `/reservations/search?themeId&memberId&dateFrom&dateTo` | 예약 정보 조건 검색 | | `@RestController` | | ||
| `MEMBER` | GET | `/token-reissue` | JWT 토큰 재발급 | | `@RestController` | | ||
| `ADMIN` | GET | `/reservations` | 예약 정보 조회 | | `@RestController` | | ||
| `MEMBER` | GET | `/reservations-mine` | 내 예약 정보 조회 | | `@RestController` | | ||
| `ADMIN` | GET | `/reservations/search?themeId&memberId&dateFrom&dateTo` | 예약 정보 조건 검색 | | `@RestController` | | ||
| | GET | `/reservations/themes/{themeId}/reservationTimes?date` | 특정 날짜의 특정 테마 예약 정보 조회 | | `@RestController` | | ||
| `MEMBER` | POST | `/reservations` | 예약 추가 | | `@RestController` | | ||
| | DELETE | `/reservations/{id}` | 예약 취소 | | `@RestController` | | ||
|
@@ -55,6 +60,57 @@ Set-Cookie: refreshToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6ImFkbWluIi | |
|
||
--- | ||
|
||
### 회원가입 요청 API | ||
|
||
- Request | ||
|
||
``` | ||
POST /login HTTP/1.1 | ||
Content-Type: application/json | ||
|
||
{ | ||
"name: "name" | ||
"password": "password", | ||
"email": "admin@email.com" | ||
} | ||
``` | ||
|
||
- Response | ||
|
||
``` | ||
HTTP/1.1 200 | ||
Content-Type: application/json | ||
Keep-Alive: timeout=60 | ||
Set-Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6ImFkbWluIiwicm9sZSI6IkFETUlOIn0.cwnHsltFeEtOzMHs2Q5-ItawgvBZ140OyWecppNlLoI; Path=/; HttpOnly | ||
Set-Cookie: refreshToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6ImFkbWluIiwicm9sZSI6IkFETUlOIn0.cwnHsltFeEtOzMHs2Q5-ItawgvBZ140OyWecppNlLoI; Path=/; HttpOnly | ||
``` | ||
|
||
--- | ||
|
||
### 로그아웃 요청 API | ||
|
||
- Request | ||
|
||
``` | ||
POST /login HTTP/1.1 | ||
Content-Type: application/json | ||
accessToken=eyJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNzE1NjE1OTMyLCJleHAiOjE3MTU2MTc3MzJ9.nfu6IZlKBccnmBbMtKDTP-5TbNWUMhcVY_ee09aNwhE; | ||
``` | ||
|
||
- Response | ||
|
||
> Cookie를 통해 만료된 토큰 Response | ||
|
||
``` | ||
HTTP/1.1 200 | ||
Content-Type: application/json | ||
Keep-Alive: timeout=60 | ||
Set-Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6ImFkbWluIiwicm9sZSI6IkFETUlOIn0.cwnHsltFeEtOzMHs2Q5-ItawgvBZ140OyWecppNlLoI; Path=/; HttpOnly | ||
Set-Cookie: refreshToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6ImFkbWluIiwicm9sZSI6IkFETUlOIn0.cwnHsltFeEtOzMHs2Q5-ItawgvBZ140OyWecppNlLoI; Path=/; HttpOnly | ||
``` | ||
|
||
--- | ||
|
||
### JWT 토큰 재발급 API | ||
|
||
- Request | ||
|
@@ -134,6 +190,48 @@ Content-Type: application/json | |
|
||
--- | ||
|
||
### 내 예약 정보 조회 API | ||
|
||
- Request | ||
|
||
``` | ||
GET /reservations-mine HTTP/1.1 | ||
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. 주어진 엔드포인트에 대해서 어떻게 생각하세요? 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. 해당 API endpoint는 RESTful 하지 않다고 느껴집니다. |
||
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwibmFtZSI6IuyWtOuTnOuvvCIsInJvbGUiOiJBRE1JTiJ9.vcK93ONRQYPFCxT5KleSM6b7cl1FE-neSLKaFyslsZM; | ||
``` | ||
|
||
- Response | ||
|
||
``` | ||
HTTP/1.1 200 | ||
Content-Type: application/json | ||
|
||
[ | ||
{ | ||
"reservationId": 1, | ||
"theme": "테마1", | ||
"date": "2024-03-01", | ||
"time": "10:00", | ||
"status": "예약" | ||
}, | ||
{ | ||
"reservationId": 2, | ||
"theme": "테마2", | ||
"date": "2024-03-01", | ||
"time": "12:00", | ||
"status": "예약" | ||
}, | ||
{ | ||
"reservationId": 3, | ||
"theme": "테마3", | ||
"date": "2024-03-01", | ||
"time": "14:00", | ||
"status": "예약" | ||
} | ||
] | ||
``` | ||
|
||
--- | ||
|
||
### 예약 정보 조회 API | ||
|
||
- Request | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,18 @@ | |
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import jakarta.validation.Valid; | ||
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.dto.LoginCheckResponse; | ||
import roomescape.auth.dto.LoginRequest; | ||
import roomescape.auth.dto.SignUpRequest; | ||
import roomescape.auth.service.AuthService; | ||
import roomescape.global.auth.annotation.Auth; | ||
import roomescape.global.auth.annotation.MemberId; | ||
import roomescape.global.auth.jwt.JwtHandler; | ||
import roomescape.global.auth.jwt.dto.TokenDto; | ||
import roomescape.global.dto.response.ApiResponse; | ||
|
||
|
@@ -22,20 +26,38 @@ public AuthController(final AuthService authService) { | |
this.authService = authService; | ||
} | ||
|
||
@PostMapping("/signup") | ||
public ApiResponse<Void> signup(@Valid @RequestBody final SignUpRequest signupRequest, final HttpServletResponse response) { | ||
TokenDto tokenDto = authService.signUp(signupRequest); | ||
addTokensToCookie(tokenDto, response); | ||
|
||
return ApiResponse.success(); | ||
} | ||
|
||
@PostMapping("/login") | ||
public ApiResponse<Void> login(@RequestBody final LoginRequest loginRequest, final HttpServletResponse response) { | ||
public ApiResponse<Void> login(@Valid @RequestBody final LoginRequest loginRequest, final HttpServletResponse response) { | ||
TokenDto tokenDto = authService.login(loginRequest); | ||
addTokensToCookie(tokenDto, response); | ||
|
||
return ApiResponse.success(); | ||
} | ||
|
||
@PostMapping("/logout") | ||
public ApiResponse<Void> logout(final HttpServletResponse response) { | ||
TokenDto logoutTokenDto = authService.logout(); | ||
addTokensToCookie(logoutTokenDto, response); | ||
|
||
return ApiResponse.success(); | ||
} | ||
|
||
@Auth | ||
@GetMapping("/login/check") | ||
public ApiResponse<LoginCheckResponse> checkLogin(@MemberId final Long memberId) { | ||
LoginCheckResponse response = authService.checkLogin(memberId); | ||
return ApiResponse.success(response); | ||
} | ||
|
||
// TODO: 토큰 재발급 자동화 로직 구현 | ||
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. 요건 왜 TODO예요? 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. 클라이언트에서 401 Unauthorized가 발생하면 |
||
@GetMapping("/token-reissue") | ||
public ApiResponse<Void> reissueToken(final HttpServletRequest request, final HttpServletResponse response) { | ||
TokenDto requestToken = getTokenFromCookie(request); | ||
|
@@ -50,27 +72,26 @@ private TokenDto getTokenFromCookie(final HttpServletRequest request) { | |
String accessToken = ""; | ||
String refreshToken = ""; | ||
for (Cookie cookie : request.getCookies()) { | ||
if (cookie.getName().equals("accessToken")) { | ||
if (cookie.getName().equals(JwtHandler.ACCESS_TOKEN_HEADER_KEY)) { | ||
accessToken = cookie.getValue(); | ||
cookie.setMaxAge(0); | ||
} | ||
if (cookie.getName().equals("refreshToken")) { | ||
if (cookie.getName().equals(JwtHandler.REFRESH_TOKEN_HEADER_KEY)) { | ||
refreshToken = cookie.getValue(); | ||
cookie.setMaxAge(0); | ||
} | ||
} | ||
|
||
return new TokenDto(accessToken, refreshToken); | ||
} | ||
|
||
private void addTokensToCookie(TokenDto tokenInfo, HttpServletResponse response) { | ||
addTokenToCookie("accessToken", tokenInfo.accessToken(), response); | ||
addTokenToCookie("refreshToken", tokenInfo.refreshToken(), response); | ||
addTokenToCookie(JwtHandler.ACCESS_TOKEN_HEADER_KEY, tokenInfo.accessToken(), response, JwtHandler.ACCESS_TOKEN_EXPIRE_TIME); | ||
addTokenToCookie(JwtHandler.REFRESH_TOKEN_HEADER_KEY, tokenInfo.refreshToken(), response, JwtHandler.REFRESH_TOKEN_EXPIRE_TIME); | ||
} | ||
|
||
private void addTokenToCookie(String cookieName, String token, HttpServletResponse response) { | ||
private void addTokenToCookie(String cookieName, String token, HttpServletResponse response, int maxAge) { | ||
Cookie cookie = new Cookie(cookieName, token); | ||
cookie.setHttpOnly(true); | ||
cookie.setMaxAge(maxAge); | ||
|
||
response.addCookie(cookie); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
package roomescape.auth.dto; | ||
|
||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
|
||
public record LoginRequest( | ||
@NotBlank(message = "이메일은 null 또는 공백일 수 없습니다.") | ||
@Email(message = "이메일 형식이 일치하지 않습니다. (xxx@xxx.xxx)") | ||
String email, | ||
@NotBlank(message = "비밀번호는 null 또는 공백일 수 없습니다.") | ||
String password | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package roomescape.auth.dto; | ||
|
||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.Size; | ||
import roomescape.member.domain.Member; | ||
import roomescape.member.domain.MemberName; | ||
|
||
public record SignUpRequest( | ||
@NotBlank(message = "회원명은 null 또는 공백일 수 없습니다.") | ||
@Size(min = MemberName.MIN_LENGTH, max = MemberName.MAX_LENGTH, message = "회원명은 " + MemberName.MIN_LENGTH + "~" + MemberName.MAX_LENGTH + "글자 사이여야 합니다.") | ||
String name, | ||
@NotBlank(message = "이메일은 null 또는 공백일 수 없습니다.") | ||
@Email(message = "이메일 형식이 일치하지 않습니다. (xxx@xxx.xxx)") | ||
String email, | ||
@NotBlank(message = "비밀번호는 null 또는 공백일 수 없습니다.") | ||
String password | ||
) { | ||
public Member toMemberEntity() { | ||
return new Member( | ||
name, | ||
email, | ||
password | ||
); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package roomescape.global.auth.annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.METHOD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Auth { | ||
} |
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.
README에 구현해주신 테이블 명세에 대한 ERD를 추가해주시는건 어때요?
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.
너무 좋은 것 같습니다:)
반영해볼게요!