Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencies {
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@

import endolphin.backend.domain.discussion.dto.CreateDiscussionRequest;
import endolphin.backend.domain.discussion.dto.CreateDiscussionResponse;
import endolphin.backend.global.error.ErrorResponse;
import endolphin.backend.global.util.URIUtil;
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 jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Discussion", description = "논의 관리 API")
@RestController
@RequestMapping("/api/v1/discussion")
@RequiredArgsConstructor
public class DiscussionController {

private final DiscussionService discussionService;

@Operation(summary = "논의 생성", description = "새 논의를 생성합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "논의 생성 성공",
content = @Content(schema = @Schema(implementation = CreateDiscussionResponse.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping
public ResponseEntity<CreateDiscussionResponse> createDiscussion(
@RequestBody @Valid CreateDiscussionRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
import endolphin.backend.domain.personal_event.dto.PersonalEventRequest;
import endolphin.backend.domain.personal_event.dto.PersonalEventResponse;
import endolphin.backend.global.dto.ListResponse;
import endolphin.backend.global.error.ErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import endolphin.backend.global.util.URIUtil;
import io.swagger.v3.oas.annotations.media.ArraySchema;
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 jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
Expand All @@ -21,13 +29,27 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@Tag(name = "Personal Event", description = "개인 일정 관리 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/personal-event")
public class PersonalEventController {

private final PersonalEventService personalEventService;

@Operation(summary = "개인 일정 조회", description = "사용자의 개인 일정을 주 단위로 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "일정 조회 성공",
content = @Content(
array = @ArraySchema(
schema = @Schema(implementation = PersonalEventResponse.class)))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping
public ResponseEntity<ListResponse<PersonalEventResponse>> getPersonalEvents(
@Valid @NotNull @RequestParam LocalDateTime startDateTime,
Expand All @@ -37,6 +59,17 @@ public ResponseEntity<ListResponse<PersonalEventResponse>> getPersonalEvents(
return ResponseEntity.ok(response);
}

@Operation(summary = "개인 일정 생성", description = "새 일정을 추가합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "일정 생성 성공",
content = @Content(schema = @Schema(implementation = PersonalEventResponse.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping
public ResponseEntity<PersonalEventResponse> createPersonalEvent(
@Valid @RequestBody PersonalEventRequest request) {
Expand All @@ -45,6 +78,19 @@ public ResponseEntity<PersonalEventResponse> createPersonalEvent(
return ResponseEntity.created(location).body(response);
}

@Operation(summary = "개인 일정 수정", description = "개인 일정을 수정합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "일정 수정 성공",
content = @Content(schema = @Schema(implementation = PersonalEventResponse.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", description = "해당 일정 없음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PutMapping("/{personalEventId}")
public ResponseEntity<PersonalEventResponse> updatePersonalEvent(
@Valid @RequestBody PersonalEventRequest request,
Expand All @@ -54,6 +100,18 @@ public ResponseEntity<PersonalEventResponse> updatePersonalEvent(
return ResponseEntity.ok(response);
}

@Operation(summary = "개인 일정 삭제", description = "개인 일정을 삭제합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "일정 삭제 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", description = "해당 일정 없음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@DeleteMapping("/{personalEventId}")
public ResponseEntity<Void> deletePersonalEvent(
@PathVariable("personalEventId") Long personalEventId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@

import endolphin.backend.domain.user.dto.OAuthResponse;
import endolphin.backend.domain.user.dto.UrlResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "User", description = "사용자 관리 API")
@RestController
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@Operation(summary = "구글 로그인 URL", description = "구글 로그인 URL을 반환합니다.")
@GetMapping("/api/v1/google")
public ResponseEntity<UrlResponse> loginUrl() {
UrlResponse response = userService.getGoogleLoginUrl();
return ResponseEntity.ok(response);
}

@Operation(summary = "구글 로그인 콜백", description = "사용자가 Google 계정으로 로그인하여 JWT 토큰을 발급받습니다.")
@GetMapping("/oauth2/callback")
public ResponseEntity<OAuthResponse> oauthCallback(@RequestParam("code") String code) {
OAuthResponse response = userService.oauth2Callback(code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import endolphin.backend.domain.user.dto.UrlResponse;
import endolphin.backend.domain.user.entity.User;
import endolphin.backend.global.config.GoogleOAuthProperties;
import endolphin.backend.global.error.exception.ApiException;
import endolphin.backend.global.error.exception.ErrorCode;
import endolphin.backend.global.security.JwtProvider;
import endolphin.backend.global.security.UserContext;
import endolphin.backend.global.security.UserInfo;
Expand Down Expand Up @@ -51,7 +53,7 @@ public OAuthResponse oauth2Callback(String code) {
public User getCurrentUser() {
UserInfo userInfo = UserContext.get();
return userRepository.findById(userInfo.userId())
.orElseThrow(() -> new RuntimeException("User not found"));
.orElseThrow(() -> new ApiException(ErrorCode.USER_NOT_FOUND));
}

private User createUser(GoogleUserInfo userInfo, GoogleTokens tokenResponse) {
Expand All @@ -61,8 +63,8 @@ private User createUser(GoogleUserInfo userInfo, GoogleTokens tokenResponse) {
.email(userInfo.email())
.name(userInfo.name())
.picture(userInfo.picture())
.access_token(tokenResponse.accessToken())
.refresh_token(tokenResponse.refreshToken())
.accessToken(tokenResponse.accessToken())
.refreshToken(tokenResponse.refreshToken())
.build();
});
userRepository.save(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Entity
Expand All @@ -27,16 +28,18 @@ public class User extends BaseTimeEntity {
private String email;
@Column(nullable = false)
private String picture;
private String access_token;
private String refresh_token;
@Column(name = "access_token")
private String accessToken;
@Column(name = "refresh_token")
private String refreshToken;

@Builder
public User(String name, String email, String picture, String access_token,
String refresh_token) {
public User(String name, String email, String picture, String accessToken,
String refreshToken) {
this.name = name;
this.email = email;
this.picture = picture;
this.access_token = access_token;
this.refresh_token = refresh_token;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package endolphin.backend.global.error;

import endolphin.backend.global.error.exception.ErrorCode;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpStatus;

@Schema(description = "에러 응답")
public record ErrorResponse(
@Schema(description = "에러 메시지", example = "Internal Server Error")
String message,
@Schema(description = "에러 코드", example = "C001")
String code
) {
public static ErrorResponse of(ErrorCode errorCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public ResponseEntity<ErrorResponse> handleApiException(ApiException e) {
log.error("[API exception] Error code: {}, Message: {}",
e.getErrorCode(), e.getMessage(), e);
ErrorResponse response = ErrorResponse.of(e.getErrorCode());
return ResponseEntity.status((e.getErrorCode().getHttpStatus())).body(response);
return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(response);
}

@ExceptionHandler(Exception.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public enum ErrorCode {
// Common
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "C001", "Internal Server Error"),
INVALID_DATE_TIME_RANGE(HttpStatus.BAD_REQUEST, "C002", "End Time must be after Start Time"),

// User
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "U001", "User not found"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package endolphin.backend.global.util;

import endolphin.backend.global.error.exception.ApiException;
import endolphin.backend.global.error.exception.ErrorCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
Expand All @@ -8,19 +10,19 @@ public class Validator {

public static void validateDateTimeRange(LocalDateTime start, LocalDateTime end) {
if (end.isBefore(start)) {
throw new IllegalArgumentException("Date range cannot be before start date");
throw new ApiException(ErrorCode.INVALID_DATE_TIME_RANGE);
}
}

public static void validateDateTimeRange(LocalDate start, LocalDate end) {
if (end.isBefore(start)) {
throw new IllegalArgumentException("Date range cannot be before start date");
throw new ApiException(ErrorCode.INVALID_DATE_TIME_RANGE);
}
}

public static void validateDateTimeRange(LocalTime start, LocalTime end) {
if (end.isBefore(start)) {
throw new IllegalArgumentException("Date range cannot be before start date");
throw new ApiException(ErrorCode.INVALID_DATE_TIME_RANGE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ void oauth2Callback_ShouldReturnJwtToken() {
.email(googleUserInfo.email())
.name(googleUserInfo.name())
.picture(googleUserInfo.picture())
.access_token(googleTokens.accessToken())
.refresh_token(googleTokens.refreshToken())
.accessToken(googleTokens.accessToken())
.refreshToken(googleTokens.refreshToken())
.build();

given(restTemplate.postForEntity(eq(testTokenUrl), any(HttpEntity.class), eq(GoogleTokens.class)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.assertj.core.api.Assertions.*;

import endolphin.backend.global.error.exception.ApiException;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -25,6 +26,6 @@ void validateDateTimeRangeTest_Failure() {
LocalDateTime endTime = LocalDateTime.of(2020, 10, 31, 23, 59, 59);

assertThatThrownBy(() -> Validator.validateDateTimeRange(startTime, endTime))
.isInstanceOf(IllegalArgumentException.class);
.isInstanceOf(ApiException.class);
}
}