Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6757337
feat: CalendarConnection 엔티티 추가
Jy000n Jul 7, 2026
1241507
feat: 캘린더 ErrorCode, SuccessCode 추가
Jy000n Jul 7, 2026
ffc1855
feat: CalendarConnectionRepository 추가
Jy000n Jul 7, 2026
a9a8aff
feat: 캘린더 연동 DTO 추가
Jy000n Jul 7, 2026
138f43d
feat: 캘린더 해제 DTO 추가
Jy000n Jul 7, 2026
0602283
feat: 구글 토큰 교환 응답 DTO 추가
Jy000n Jul 7, 2026
9dba14a
feat: CalendarErrorCode에 이메일 불일치 에러 코드 추가
Jy000n Jul 7, 2026
dca234c
fix: CalendarConnectResponse 필드명 수정
Jy000n Jul 7, 2026
8d1696d
feat: GoogleUserInfoResponse 응답 추가
Jy000n Jul 7, 2026
85f793b
feat: GoogleOAuthClient로 토큰 교환/사용자 정보 조회/revoke 로직 분리
Jy000n Jul 7, 2026
b26dad2
chore: 불필요한 import, 빈칸 제거
Jy000n Jul 7, 2026
c5a262a
feat: 구글 캘린더 연동/해제 Service 로직 구현 (가입 이메일 일치 검증 포함)
Jy000n Jul 7, 2026
d0ad363
feat: 캘린더 연동/해제 응답 조립을 위한 CalendarResponseFactory 추가
Jy000n Jul 7, 2026
84a388a
docs: CalendarControllerDocs Swagger 문서 인터페이스 추가
Jy000n Jul 7, 2026
7d16e4d
feat: 구글 캘린더 연동/해제 API 엔드포인트 추가
Jy000n Jul 7, 2026
07282c5
fix: user 연관관계 매핑을 @JoinColumn으로 수정
Jy000n Jul 7, 2026
db14d2f
docs: Swagger 문서 내용 수정
Jy000n Jul 7, 2026
7e8a7f1
Merge branch 'develop' of https://github.com/Team-Timo/Timo-Server in…
Jy000n Jul 7, 2026
69ce4a5
docs: Swagger 문서 내용 수정
Jy000n Jul 7, 2026
eb7da00
fix: CalendarConnectResponse connectedAt 응답 포맷을 yyyy-MM-dd HH:mm:ss로 통일
Jy000n Jul 7, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.Timo.Timo.domain.calendar.client;

import com.Timo.Timo.domain.calendar.dto.response.GoogleTokenResponse;
import com.Timo.Timo.domain.calendar.dto.response.GoogleUserInfoResponse;
import com.Timo.Timo.domain.calendar.exception.CalendarErrorCode;
import com.Timo.Timo.global.exception.CustomException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;

@Component
public class GoogleOAuthClient {

private final RestClient restClient = RestClient.create();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

RestClient에 타임아웃이 설정되지 않아 스레드 블로킹 위험이 있습니다.

RestClient.create()는 기본 connect/read 타임아웃이 없어, Google API 응답 지연 시 요청 스레드가 무한 대기합니다. 다수 사용자가 동시에 연동/해제 시도 시 Tomcat 스레드 풀 고갈로 전체 서비스 장애가 발생할 수 있습니다.

⏱️ 제안: 타임아웃 설정 추가
+import java.time.Duration;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+
 `@Component`
 public class GoogleOAuthClient {

-  private final RestClient restClient = RestClient.create();
+  private final RestClient restClient = RestClient.builder()
+      .requestFactory(buildRequestFactory())
+      .build();
+
+  private SimpleClientHttpRequestFactory buildRequestFactory() {
+    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+    factory.setConnectTimeout(Duration.ofSeconds(5));
+    factory.setReadTimeout(Duration.ofSeconds(10));
+    return factory;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private final RestClient restClient = RestClient.create();
import java.time.Duration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
private final RestClient restClient = RestClient.builder()
.requestFactory(buildRequestFactory())
.build();
private SimpleClientHttpRequestFactory buildRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Duration.ofSeconds(5));
factory.setReadTimeout(Duration.ofSeconds(10));
return factory;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/domain/calendar/client/GoogleOAuthClient.java` at
line 16, `GoogleOAuthClient`의 `restClient`가 `RestClient.create()`로 기본 설정되어 있어
connect/read 타임아웃이 없어 요청 스레드가 블로킹될 수 있습니다. `GoogleOAuthClient` 생성 방식 또는
`restClient` 초기화를 수정해 `RestClient`에 연결 및 읽기 타임아웃을 명시적으로 설정하고, Google API 호출이 지연될
때도 요청이 무한 대기하지 않도록 하세요. 필요하면 이 클라이언트를 사용하는 토큰 교환/연동 해제 로직에서도 동일한 `restClient`
설정이 적용되는지 함께 확인하세요.


@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String clientId;

@Value("${spring.security.oauth2.client.registration.google.client-secret}")
private String clientSecret;

@Value("${app.oauth2.redirect-uri}")
private String redirectUri;

public GoogleTokenResponse exchangeToken(String authorizationCode){
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("code", authorizationCode);
body.add("client_id", clientId);
body.add("client_secret", clientSecret);
body.add("redirect_uri", redirectUri);
body.add("grant_type", "authorization_code");

try {
return restClient.post()
.uri("https://oauth2.googleapis.com/token")
.body(body)
.retrieve()
.body(GoogleTokenResponse.class);
} catch (Exception e) {
throw new CustomException(CalendarErrorCode.CALENDAR_401_AUTH_FAILED);
}
}

public GoogleUserInfoResponse fetchUserInfo(String accessToken){
try {
return restClient.get()
.uri("https://www.googleapis.com/oauth2/v2/userinfo")
.header("Authorization", "Bearer " + accessToken)
.retrieve()
.body(GoogleUserInfoResponse.class);
} catch (Exception e) {
throw new CustomException(CalendarErrorCode.CALENDAR_401_AUTH_FAILED);
}
}

public void revokeToken(String token) {
try {
restClient.post()
.uri("https://oauth2.googleapis.com/revoke?token=" + token)
.retrieve()
.toBodilessEntity();
} catch (Exception e) {
throw new CustomException(CalendarErrorCode.CALENDAR_500_REVOKE_FAILED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.Timo.Timo.domain.calendar.controller;

import com.Timo.Timo.domain.calendar.docs.CalendarControllerDocs;
import com.Timo.Timo.domain.calendar.dto.request.CalendarConnectRequest;
import com.Timo.Timo.domain.calendar.dto.response.CalendarConnectResponse;
import com.Timo.Timo.domain.calendar.dto.response.CalendarDisconnectResponse;
import com.Timo.Timo.domain.calendar.factory.CalendarResponseFactory;
import com.Timo.Timo.domain.calendar.service.CalendarService;
import com.Timo.Timo.global.auth.principal.CustomUserDetails;
import com.Timo.Timo.global.response.BaseResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
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/users/calendar")
@RequiredArgsConstructor
@Tag(name = "Calendar", description = "구글 캘린더 연동 API")
public class CalendarController implements CalendarControllerDocs {

private final CalendarService calendarService;
private final CalendarResponseFactory calendarResponseFactory;

@Override
@PostMapping
public ResponseEntity<BaseResponse<CalendarConnectResponse>> connectCalendar(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Valid @RequestBody CalendarConnectRequest request
) {
Long userId = userDetails.getUserId();
CalendarConnectResponse response = calendarService.connect(userId, request);

return calendarResponseFactory.connectResponse(response);
}

@Override
@DeleteMapping
public ResponseEntity<BaseResponse<CalendarDisconnectResponse>> disconnectCalendar(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Long userId = userDetails.getUserId();
CalendarDisconnectResponse response = calendarService.disconnect(userId);

return calendarResponseFactory.disconnectResponse(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.Timo.Timo.domain.calendar.docs;

import com.Timo.Timo.domain.calendar.dto.request.CalendarConnectRequest;
import com.Timo.Timo.domain.calendar.dto.response.CalendarConnectResponse;
import com.Timo.Timo.domain.calendar.dto.response.CalendarDisconnectResponse;
import com.Timo.Timo.global.auth.principal.CustomUserDetails;
import com.Timo.Timo.global.exception.dto.ErrorDto;
import com.Timo.Timo.global.response.BaseResponse;
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.security.SecurityRequirement;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

public interface CalendarControllerDocs {

@Operation(
summary = "구글 캘린더 연동",
description = """
구글 OAuth 동의 완료 후 발급된 authorizationCode로 구글 토큰을 교환하여 캘린더 연동합니다.
가입 시 사용한 구글 계정과 다른 계정으로 연동을 시도하면 거부됩니다.
""",
security = @SecurityRequirement(name = "bearerAuth")
)
@ApiResponses({
@ApiResponse(responseCode = "201", description = "연동 성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "400", description = "authorizationCode 누락",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class))),
@ApiResponse(responseCode = "401", description = "구글 인증 실패, 토큰 없음/만료, 또는 가입 이메일과 다른 계정으로 연동 시도",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class))),
@ApiResponse(responseCode = "409", description = "이미 캘린더가 연동된 상태",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class))),
@ApiResponse(responseCode = "500", description = "서버 내부 오류",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class)))
})
ResponseEntity<BaseResponse<CalendarConnectResponse>> connectCalendar(
@Parameter(hidden = true) CustomUserDetails userDetails,
@Valid @RequestBody CalendarConnectRequest request
);

@Operation(
summary = "구글 캘린더 연동 해제",
description = """
연동된 구글 캘린더 정보를 삭제하고 구글 토큰을 revoke합니다.
""",
security = @SecurityRequirement(name = "bearerAuth")
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "연동 해제 성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "401", description = "토큰 없음/만료/유효하지 않음",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class))),
@ApiResponse(responseCode = "404", description = "연동된 캘린더가 없는 경우",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class))),
@ApiResponse(responseCode = "500", description = "서버 내부 오류",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class)))
})
ResponseEntity<BaseResponse<CalendarDisconnectResponse>> disconnectCalendar(
@Parameter(hidden = true) CustomUserDetails userDetails
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.Timo.Timo.domain.calendar.dto.request;

import jakarta.validation.constraints.NotBlank;

public record CalendarConnectRequest(
@NotBlank(message = "authorizationCode는 필수입니다.")
String authorizationCode
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.Timo.Timo.domain.calendar.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Builder;

@Builder
public record CalendarConnectResponse(
boolean calendarConnected,
String calendarEmail,

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(example = "2026-07-06 17:51:50", type = "string")
LocalDateTime connectedAt
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.Timo.Timo.domain.calendar.dto.response;

import lombok.Builder;

@Builder
public record CalendarDisconnectResponse(
boolean calendarConnected
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.Timo.Timo.domain.calendar.dto.response;

public record GoogleTokenResponse (
String accessToken,
String refreshToken,
Integer expiresIn
){}
Comment on lines +3 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 전역 Jackson naming strategy 설정 확인
rg -i "naming-strategy|SNAKE_CASE" -g '*.yml' -g '*.yaml' -g '*.properties' -n || echo "No naming strategy found"

# `@JsonNaming` 사용 패턴 확인
rg -l "`@JsonNaming`" --type java -g '!**/test/**' || echo "No `@JsonNaming` usage found"

# RestClient 빈 구성 확인
rg -n "RestClient" --type java -g '!**/test/**' -g '!**/build/**'

Repository: Team-Timo/Timo-Server

Length of output: 184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== GoogleTokenResponse ==\n'
sed -n '1,80p' src/main/java/com/Timo/Timo/domain/calendar/dto/response/GoogleTokenResponse.java

printf '\n== RestClient / ObjectMapper configuration ==\n'
rg -n "RestClient|ObjectMapper|Jackson2ObjectMapperBuilder|JsonNaming|JsonProperty|SnakeCaseStrategy|PropertyNamingStrategies" src/main/java -g '!**/test/**' -g '!**/build/**'

printf '\n== Google token usage ==\n'
rg -n "GoogleTokenResponse|access_token|refresh_token|expires_in|token" src/main/java -g '!**/test/**' -g '!**/build/**'

Repository: Team-Timo/Timo-Server

Length of output: 7536


src/main/java/com/Timo/Timo/domain/calendar/dto/response/GoogleTokenResponse.java: snake_case 매핑을 추가하세요. GoogleTokenResponse는 Google의 access_token, refresh_token, expires_in을 받는데 camelCase record라 매칭되지 않습니다. 이대로면 토큰과 만료 시간이 null로 들어와 뒤이어 fetchUserInfo(...)plusSeconds(...)가 실패합니다. @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) 또는 @JsonProperty를 붙여주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/Timo/Timo/domain/calendar/dto/response/GoogleTokenResponse.java`
around lines 3 - 7, `GoogleTokenResponse` is missing snake_case JSON mapping, so
Google’s `access_token`, `refresh_token`, and `expires_in` fields won’t bind to
the camelCase record components and can end up null. Update the
`GoogleTokenResponse` record to use Jackson snake_case mapping by adding
`@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)` or equivalent
`@JsonProperty` annotations on the record components so the fields populate
correctly for the code paths used by `fetchUserInfo(...)` and
`plusSeconds(...)`.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.Timo.Timo.domain.calendar.dto.response;

public record GoogleUserInfoResponse(
String email
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.Timo.Timo.domain.calendar.entity;

import com.Timo.Timo.domain.user.entity.User;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "calendar_connections")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CalendarConnection {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@OneToOne
@JoinColumn(name = "user_id", nullable = false, unique = true)
private User user;

@Column(name = "calendar_email", nullable = false)
private String calendarEmail;

@Column(name = "access_token", nullable = false, length = 2048)
private String accessToken;

@Column(name = "refresh_token", length = 2048)
private String refreshToken;

@Column(name = "token_expires_at")
private LocalDateTime tokenExpiresAt;

@Column(name = "connected_at", nullable = false)
private LocalDateTime connectedAt;

@Builder
private CalendarConnection(
User user,
String calendarEmail,
String accessToken,
String refreshToken,
LocalDateTime tokenExpiresAt
) {
this.user = user;
this.calendarEmail = calendarEmail;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.tokenExpiresAt = tokenExpiresAt;
this.connectedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.Timo.Timo.domain.calendar.exception;

import com.Timo.Timo.global.exception.code.BaseErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum CalendarErrorCode implements BaseErrorCode {

CALENDAR_401_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "구글 캘린더 인증에 실패했습니다."),
CALENDAR_401_EMAIL_MISMATCH(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "가입 시 사용한 구글 계정으로만 연동할 수 있습니다."),
Comment on lines +12 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

중복된 에러 코드 "CALENDAR_401"로 클라이언트가 에러를 구분할 수 없습니다.

CALENDAR_401_AUTH_FAILEDCALENDAR_401_EMAIL_MISMATCH가 동일한 code 값을 공유합니다. 클라이언트가 code만으로 두 케이스를 구분할 수 없어 message 파싱에 의존해야 하므로, 이메일 불일치 케이스에 대한 별도 사용자 안내가 어렵습니다.

🔧 제안: 에러 코드 분리
   CALENDAR_401_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "구글 캘린더 인증에 실패했습니다."),
-  CALENDAR_401_EMAIL_MISMATCH(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "가입 시 사용한 구글 계정으로만 연동할 수 있습니다."),
+  CALENDAR_401_EMAIL_MISMATCH(HttpStatus.UNAUTHORIZED, "CALENDAR_401_EMAIL", "가입 시 사용한 구글 계정으로만 연동할 수 있습니다."),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CALENDAR_401_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "구글 캘린더 인증에 실패했습니다."),
CALENDAR_401_EMAIL_MISMATCH(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "가입 시 사용한 구글 계정으로만 연동할 수 있습니다."),
CALENDAR_401_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "CALENDAR_401", "구글 캘린더 인증에 실패했습니다."),
CALENDAR_401_EMAIL_MISMATCH(HttpStatus.UNAUTHORIZED, "CALENDAR_401_EMAIL", "가입 시 사용한 구글 계정으로만 연동할 수 있습니다."),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/Timo/Timo/domain/calendar/exception/CalendarErrorCode.java`
around lines 12 - 13, `CalendarErrorCode` has two distinct enum entries,
`CALENDAR_401_AUTH_FAILED` and `CALENDAR_401_EMAIL_MISMATCH`, but they currently
share the same code value, so clients cannot distinguish the failure cases
reliably. Update the `CalendarErrorCode` enum so `CALENDAR_401_EMAIL_MISMATCH`
uses its own unique code string while keeping the existing HTTP status and
message, and make sure any references that consume `getCode()` or compare
calendar error codes are consistent with the new identifier.

CALENDAR_404_NOT_CONNECTED(HttpStatus.NOT_FOUND, "CALENDAR_404", "연동된 캘린더가 없습니다."),
CALENDAR_409_ALREADY_CONNECTED(HttpStatus.CONFLICT, "CALENDAR_409", "이미 캘린더가 연동되어 있습니다."),
CALENDAR_500_REVOKE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "CALENDAR_500", "구글 토큰 해제에 실패했습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.Timo.Timo.domain.calendar.exception;

import com.Timo.Timo.global.exception.code.BaseSuccessCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum CalendarSuccessCode implements BaseSuccessCode {

CALENDAR_CONNECTED(HttpStatus.CREATED, "구글 캘린더가 연동되었습니다."),
CALENDAR_DISCONNECTED(HttpStatus.OK, "구글 캘린더 연동이 해제되었습니다.");

private final HttpStatus httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.Timo.Timo.domain.calendar.factory;

import com.Timo.Timo.domain.calendar.dto.response.CalendarConnectResponse;
import com.Timo.Timo.domain.calendar.dto.response.CalendarDisconnectResponse;
import com.Timo.Timo.domain.calendar.exception.CalendarSuccessCode;
import com.Timo.Timo.global.response.BaseResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

@Component
public class CalendarResponseFactory {

public ResponseEntity<BaseResponse<CalendarConnectResponse>> connectResponse(
CalendarConnectResponse response
) {
return ResponseEntity.status(CalendarSuccessCode.CALENDAR_CONNECTED.getHttpStatus())
.body(BaseResponse.onSuccess(CalendarSuccessCode.CALENDAR_CONNECTED, response));
}

public ResponseEntity<BaseResponse<CalendarDisconnectResponse>> disconnectResponse(
CalendarDisconnectResponse response
) {
return ResponseEntity.ok(
BaseResponse.onSuccess(CalendarSuccessCode.CALENDAR_DISCONNECTED, response)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.Timo.Timo.domain.calendar.repository;

import com.Timo.Timo.domain.calendar.entity.CalendarConnection;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CalendarConnectionRepository extends JpaRepository<CalendarConnection, Long> {

Optional<CalendarConnection> findByUserId(Long userId);
boolean existsByUserId(Long userId);
}
Loading