Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/SWYP-4rd-6/backend into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
itavita08 committed May 10, 2024
2 parents a2e0c3d + f607284 commit 8d82682
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 90 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.0.1
version=0.1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.swygbro.trip.backend.domain.guideProduct.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.ZonedDateTime;


/**
* 유저 프로필에서 가이드 상품을 보여주기 위한 DTO
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SimpleGuideProductDto {
@Schema(description = "가이드 상품 ID", example = "1")
private Long id;
@Schema(description = "가이드 상품 제목", example = "상품 이름")
private String title;
@Schema(description = "가이드 상품 설명", example = "설명")
private String description;
@Schema(description = "가이드 상품 섬네일", example = "https://S3저장소URL/저장위치/난수화된 이미지이름.이미지 타입")
private String thumb;
// private Point location; // TODO 위치 정보 추가
@Schema(description = "가이드 상품 시작 시간", example = "2024-05-05 00:00:00")
private ZonedDateTime guideStart;
@Schema(description = "가이드 상품 종료 시간", example = "2024-05-05 00:00:00")
private ZonedDateTime guideEnd;
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package com.swygbro.trip.backend.domain.user.api;

import com.swygbro.trip.backend.domain.user.application.LoginService;
import com.swygbro.trip.backend.domain.user.application.UserService;
import com.swygbro.trip.backend.domain.user.dto.CreateUserRequest;
import com.swygbro.trip.backend.domain.user.dto.LoginRequest;
import com.swygbro.trip.backend.global.document.ValidationErrorResponse;
import com.swygbro.trip.backend.global.exception.ApiErrorResponse;
import com.swygbro.trip.backend.global.jwt.dto.TokenDto;
import com.swygbro.trip.backend.infra.discordbot.DiscordMessageProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
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;

@RestController
@RequestMapping("/api/v1/login")
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Tag(name = "Login", description = "로그인 관련 API")
public class LoginController {

private final LoginService loginService;
private final UserService userService;
private final DiscordMessageProvider discordMessageProvider;

@PostMapping
@Operation(summary = "로그인", description = """

@PostMapping("/login")
@Operation(summary = "이메일 로그인", description = """
# 로그인
Expand All @@ -31,15 +45,84 @@ public class LoginController {
- 로그인 성공 시 `200` 코드와 함께 토큰을 반환합니다.
- 토큰은 `access_token`과 `refresh_token`으로 구성되어 있습니다.
- 로그인 실패 시 `400` 에러를 반환합니다.
- 계정이 존재하지 않거나 비밀번호가 일치하지 않을 경우 발생합니다.
""")
@ApiResponse(
responseCode = "200",
description = "로그인 성공 시 토큰을 반환합니다."
description = "로그인 성공 시 토큰을 반환합니다.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = TokenDto.class),
examples = @ExampleObject(value = """
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMDFAZW1haWwuY29tIiwiZXhwIjoxNzE1MDA2OTc4LCJpYXQiOjE3MTUwMDMzNzh9.4mVSMEiBNmbD0VXo0w5ZtOl45Qu5V1a4dZEArkibbIQ",
"refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMDFAZW1haWwuY29tIiwiZXhwIjoxNzE1MDg5Nzc4LCJpYXQiOjE3MTUwMDMzNzh9.GK01DHHZYxYc5FXl_c5Aq0qJg9YbUoSl-U6YCj8L7ik"
}
""")
)
)
@ApiResponse(
responseCode = "400",
description = "",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ApiErrorResponse.class),
examples = @ExampleObject(value = """
{
"status": "BAD_REQUEST",
"message": "로그인에 실패했습니다."
}
""")
)
)
public TokenDto login(@Valid @RequestBody LoginRequest dto) {
return loginService.login(dto);
}

@PostMapping("/signup")
@Operation(summary = "회원 가입", description = """
# 회원가입
회원을 생성합니다. 회원 가입 시 사용자 이메일, 닉네임, 이름, 전화번호, 국적, 성별, 비밀번호, 비밀번호 확인을 입력합니다.
각 필드의 제약 조건은 다음과 같습니다.
| 필드명 | 설명 | 제약조건 | 중복확인 | 예시 |
|--------|------|----------|----------|------|
|email| 사용자의 이메일 | 이메일 형식 | Y | [email protected] |
|nickname| 다른 사용자들에게 보이는 닉네임 | 4~20자 | Y | nickname01 |
|name| 사용자의 이름 | 2~20자 | N | name01 |
|phone| 사용자의 전화번호 | '-'를 제외한 숫자 | Y | 01012345678 |
|nationality| 사용자의 국적 | 영문3자 국가 코드 | N | KOR |
|gender| 성별 | Male, Female 중 하나 | N | Male |
|password| 사용자의 비밀번호 | 영문(대소문자), 숫자, 특수문자를 포함한 8~32자 | N | password01! |
|passwordCheck| 사용자의 비밀번호 확인 | password와 동일한 입력 | N | password01! |
## 응답
- 회원 가입 성공 시 `200` 코드와 함께 회원 이메일을 문자열로 반환합니다.
- 중복된 값이 있을 경우 `409` 에러를 반환합니다.
- 입력 양식에 오류가 있을 경우 `400` 에러를 반환합니다.
""")
@ApiResponse(
responseCode = "200",
description = "생성한 계정 고유 번호를 반환합니다.",
content = @Content(
mediaType = MediaType.TEXT_PLAIN_VALUE,
schema = @Schema(implementation = Long.class, example = "1")))
@ApiResponse(
responseCode = "409",
description = "입력 값 중 중복된 값이 있습니다.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ApiErrorResponse.class),
examples = @ExampleObject(value = "{\n \"status\": \"CONFLICT\",\n \"message\": \"데이터 중복\"\n}")
)
)
@ValidationErrorResponse
public Long createUser(@Valid @RequestBody CreateUserRequest dto) {
return userService.createUser(dto);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.swygbro.trip.backend.domain.user.application.GoogleOauthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -15,18 +22,80 @@
@RestController
@RequestMapping("/api/v1/oauth2")
@RequiredArgsConstructor
@Tag(name = "Login", description = "로그인 관련 API")
public class Oauth2Controller {

private final GoogleOauthService googleOauthService;

@GetMapping("/google")
@Operation(summary = "구글 로그인", description = """
# 구글 로그인
- 이 API를 호출하면 구글 로그인 페이지로 리다이렉트됩니다.
- 이 API를 구글 로그인 버튼의 링크로 사용합니다.
- 구글 로그인 페이지에서 로그인을 완료하면 콜백 URL로 리다이렉트됩니다.
- 콜백 URL에서 구글 인가 코드를 받아서 처리합니다.
""")

@ApiResponse(
responseCode = "302",
headers = {
@Header(
name = "Location",
description = """
- 구글 로그인 페이지 URL
- ex) https://accounts.google.com/o/oauth2/v2/auth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=openid profile email
""")
},
description = "구글 로그인 페이지로 리다이렉트됩니다.")
public void redirectGoogle(HttpServletResponse response) throws IOException {
response.sendRedirect(googleOauthService.getGoogleLoginUrl());
}

@GetMapping("/callback")
public void callback(@RequestParam String code) throws JsonProcessingException {
googleOauthService.callback(code);
@Operation(summary = "구글 로그인 콜백", description = """
- 프론트엔드에서 테스트 필요
- 301 응답 시 회원가입 페이지로 이동 및 응답 데이터를 받아서 회원가입 페이지에서 사용할 수 있는지...
---
# 구글 로그인 콜백
- 구글 로그인 페이지에서 로그인을 완료하면 구글 인가 코드와 함께 이 API로 리다이렉트됩니다.
- 구글 인가 코드를 사용해 계정의 정보를 조회 및 처리합니다.
- DB에 등록된 이메일이면 로그인 처리, 등록되지 않은 이메일이면 회원가입 페이지로 이동 합니다.
- 로그인 시 200 코드와 함께 JWT 토큰을 반환합니다.
- 회원가입 시 301 코드와 함께 회원 정보를 반환합니다.
- 반환 정보는 추후 협의 필요
회원정보 반환 예시:
```json
{
"id": "114210435473335230303",
"name": "Mat thew",
"given_name": "Mat",
"family_name": "thew",
"locale": "ko",
"email": "[email protected]",
"verified_email": true,
"picture": "https://lh3.googleusercontent.com/a/ACg8ocKrQm8D09Njibbo_vgeOQZt_oBdu63c6qOgxsWZ6QOJdcflxA=s96-c"
}
""")
@Parameter(name = "code", description = "구글 인가 코드", required = true)
@ApiResponse(
responseCode = "200",
description = "로그인 성공 시 JWT 토큰을 반환합니다.",
content = @Content(mediaType = "application/json"))
@ApiResponse(
responseCode = "301",
description = "회원가입 페이지",
content = @Content(mediaType = "application/json"))
public ResponseEntity<?> callback(@RequestParam String code) throws JsonProcessingException {
return googleOauthService.callback(code);
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.swygbro.trip.backend.domain.user.api;

import com.swygbro.trip.backend.domain.user.application.UserService;
import com.swygbro.trip.backend.domain.user.dto.CreateUserRequest;
import com.swygbro.trip.backend.domain.user.dto.UpdateUserRequest;
import com.swygbro.trip.backend.domain.user.dto.UserProfileDto;
import com.swygbro.trip.backend.global.document.ForbiddenResponse;
import com.swygbro.trip.backend.global.document.InvalidTokenResponse;
import com.swygbro.trip.backend.global.document.ValidationErrorResponse;
import com.swygbro.trip.backend.global.exception.ApiErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -34,59 +32,14 @@
### 주요 기능
- 회원 가입
- 회원 정보 조회
- 회원 정보 수정
""")
public class UserController {

private final UserService userService;

@PostMapping
@Operation(summary = "회원 가입", description = """
# 회원가입
회원을 생성합니다. 회원 가입 시 사용자 이메일, 닉네임, 이름, 전화번호, 국적, 성별, 비밀번호, 비밀번호 확인을 입력합니다.
각 필드의 제약 조건은 다음과 같습니다.
| 필드명 | 설명 | 제약조건 | 중복확인 | 예시 |
|--------|------|----------|----------|------|
|email| 사용자의 이메일 | 이메일 형식 | Y | [email protected] |
|nickname| 다른 사용자들에게 보이는 닉네임 | 4~20자 | Y | nickname01 |
|name| 사용자의 이름 | 2~20자 | N | name01 |
|phone| 사용자의 전화번호 | '-'를 제외한 숫자 | Y | 01012345678 |
|nationality| 사용자의 국적 | 영문3자 국가 코드 | N | KOR |
|gender| 성별 | Male, Female 중 하나 | N | Male |
|password| 사용자의 비밀번호 | 영문(대소문자), 숫자, 특수문자를 포함한 8~32자 | N | password01! |
|passwordCheck| 사용자의 비밀번호 확인 | password와 동일한 입력 | N | password01! |
## 응답
- 회원 가입 성공 시 `200` 코드와 함께 회원 이메일을 문자열로 반환합니다.
- 중복된 값이 있을 경우 `409` 에러를 반환합니다.
- 입력 양식에 오류가 있을 경우 `400` 에러를 반환합니다.
""")
@ApiResponse(
responseCode = "200",
description = "생성한 계정 고유 번호를 반환합니다.",
content = @Content(
mediaType = MediaType.TEXT_PLAIN_VALUE,
schema = @Schema(implementation = Long.class, example = "1")))
@ApiResponse(
responseCode = "409",
description = "입력 값 중 중복된 값이 있습니다.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ApiErrorResponse.class),
examples = @ExampleObject(value = "{\n \"status\": \"CONFLICT\",\n \"message\": \"데이터 중복\"\n}")
)
)
@ValidationErrorResponse
public Long createUser(@Valid @RequestBody CreateUserRequest dto) {
return userService.createUser(dto);
}

@GetMapping("/{userId}")
@Operation(summary = "사용자 조회", description = "사용자의 프로필을 조회합니다.")
@Parameters({
Expand All @@ -112,8 +65,8 @@ public Long createUser(@Valid @RequestBody CreateUserRequest dto) {
)
)
)
public UserProfileDto getUser(@PathVariable Long userId) {
return userService.getUser(userId);
public UserProfileDto getUserProfile(@PathVariable Long userId) {
return userService.getUserProfile(userId);
}

@PutMapping(value = "{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.swygbro.trip.backend.domain.user.dto.GoogleUserInfo;
import com.swygbro.trip.backend.global.jwt.TokenService;
import com.swygbro.trip.backend.global.jwt.dto.TokenDto;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -49,21 +49,20 @@ public String getGoogleLoginUrl() {
.build().toUriString();
}

public TokenDto callback(String code) throws JsonProcessingException {
public ResponseEntity<?> callback(String code) throws JsonProcessingException {
String googleAccessToken = getGoogleAccessToken(code);
GoogleUserInfo userInfo = getGoogleUserInfo(googleAccessToken);

// TODO 회원가입 / 로그인 로직 상세화
// 등록된 회원인지 확인
boolean exists = userService.existsByEmail(userInfo.getEmail());

// 등록된 회원이라면 로그인처리
if (exists) {
return tokenService.generateToken(userInfo.getEmail());
return ResponseEntity.ok(tokenService.generateToken(userInfo.getEmail()));
}

// 등록되지 않은 회원이면 회원가입 진행
return tokenService.generateToken(userInfo.getEmail());
return ResponseEntity
.status(HttpStatus.MOVED_PERMANENTLY)
.body(userInfo);
}


Expand Down
Loading

0 comments on commit 8d82682

Please sign in to comment.