Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import endolphin.backend.domain.personal_event.dto.PersonalEventRequest;
import endolphin.backend.domain.personal_event.dto.PersonalEventResponse;
import endolphin.backend.domain.personal_event.dto.SyncResponse;
import endolphin.backend.domain.personal_event.entity.PersonalEvent;
import endolphin.backend.domain.user.UserService;
import endolphin.backend.domain.user.entity.User;
import endolphin.backend.global.dto.ListResponse;
Expand Down Expand Up @@ -81,7 +82,8 @@ public ResponseEntity<ListResponse<PersonalEventResponse>> getPersonalEvents(
@PostMapping
public ResponseEntity<PersonalEventResponse> createPersonalEvent(
@Valid @RequestBody PersonalEventRequest request) {
PersonalEventResponse response = personalEventService.createWithRequest(request);
PersonalEvent personalEvent = personalEventService.createWithRequest(request);
PersonalEventResponse response = PersonalEventResponse.fromEntity(personalEvent);
URI location = URIUtil.buildResourceUri(response.id());
return ResponseEntity.created(location).body(response);
}
Expand Down Expand Up @@ -130,7 +132,8 @@ public ResponseEntity<Void> deletePersonalEvent(

@Operation(summary = "개인 일정 동기화", description = "개인 일정을 실시간으로 동기화합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "일정 동기화 성공"),
@ApiResponse(responseCode = "200", description = "일정 동기화 성공",
content = @Content(schema = @Schema(implementation = SyncResponse.class))),
@ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 오류",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import endolphin.backend.domain.personal_event.dto.PersonalEventRequest;
import endolphin.backend.domain.personal_event.dto.PersonalEventResponse;
import endolphin.backend.domain.personal_event.dto.PersonalEventWithStatus;
import endolphin.backend.domain.personal_event.dto.SyncPersonalEvent;
import endolphin.backend.domain.personal_event.dto.SyncPersonalEvent.Status;
import endolphin.backend.domain.personal_event.entity.PersonalEvent;
import endolphin.backend.domain.personal_event.enums.PersonalEventStatus;
import endolphin.backend.domain.personal_event.event.DeletePersonalEvent;
Expand All @@ -30,6 +32,7 @@
import java.util.List;
import java.util.Map;

import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -63,7 +66,7 @@ public ListResponse<PersonalEventResponse> listPersonalEvents(
return new ListResponse<>(personalEventResponseList);
}

public PersonalEventResponse createWithRequest(PersonalEventRequest request) {
public PersonalEvent createWithRequest(PersonalEventRequest request) {
User user = userService.getCurrentUser();

Validator.validateDateTimeRange(request.startDateTime(), request.endDateTime());
Expand Down Expand Up @@ -93,7 +96,7 @@ public PersonalEventResponse createWithRequest(PersonalEventRequest request) {
true);
});

return PersonalEventResponse.fromEntity(result);
return result;
}

public void createPersonalEventsForParticipants(List<User> participants,
Expand Down Expand Up @@ -171,8 +174,8 @@ private PersonalEvent updatePersonalEvent(PersonalEventRequest request,

if (isDateTimeChanged(personalEvent, request)) {
discussions.forEach(discussion -> {
personalEventPreprocessor.
preprocessOne(oldEvent, discussion, user, false);
personalEventPreprocessor
.preprocessOne(oldEvent, discussion, user, false);
personalEventPreprocessor
.preprocessOne(personalEvent, discussion, user, true);
});
Expand Down Expand Up @@ -219,19 +222,55 @@ public void preprocessPersonalEvents(User user, Discussion discussion) {
personalEventPreprocessor.preprocess(personalEvents, discussion, user);
}

public void syncWithGoogleEvents(List<GoogleEvent> googleEvents, User user,
public List<SyncPersonalEvent> syncWithGoogleEvents(List<GoogleEvent> googleEvents, User user,
String googleCalendarId) {
List<Discussion> discussions = discussionParticipantService.getDiscussionsByUserId(
user.getId());

List<SyncPersonalEvent> syncEvents = new ArrayList<>();

for (GoogleEvent googleEvent : googleEvents) {
log.info("Processing Google event: {}", googleEvent);
switch (googleEvent.status()) {
case CONFIRMED -> upsertPersonalEventByGoogleEvent(
googleEvent, discussions, user, googleCalendarId);
case CANCELLED -> deletePersonalEventByGoogleEvent(
Optional<SyncPersonalEvent> opt =
syncPersonalEventFromGoogleEvent(user, googleCalendarId, googleEvent, discussions);

opt.ifPresent(syncEvents::add);
}

return syncEvents;
}

private Optional<SyncPersonalEvent> syncPersonalEventFromGoogleEvent(
User user, String googleCalendarId, GoogleEvent googleEvent, List<Discussion> discussions) {

return switch (googleEvent.status()) {
case CONFIRMED -> {
Optional<PersonalEvent> personalEventOpt = personalEventRepository
.findByGoogleEventIdAndCalendarId(googleEvent.eventId(), googleCalendarId);

if (personalEventOpt.isPresent()) {
PersonalEvent personalEvent = personalEventOpt.get();
PersonalEvent updatedPersonalEvent = updatePersonalEvent(
PersonalEventRequest.of(googleEvent, personalEvent.getIsAdjustable()),
personalEvent, user, discussions);

yield Optional.of(SyncPersonalEvent.from(updatedPersonalEvent, Status.UPDATED));
}

PersonalEvent personalEvent =
createWithRequest(PersonalEventRequest.of(googleEvent, false));

yield Optional.of(SyncPersonalEvent.from(personalEvent, Status.CREATED));
}
case CANCELLED -> {
Optional<PersonalEvent> opt = deletePersonalEventByGoogleEvent(
googleEvent, discussions, user, googleCalendarId);

yield opt.map(
personalEvent -> SyncPersonalEvent.from(personalEvent, Status.DELETED));
}
}
case TENTATIVE -> Optional.empty();
};
}

private void validatePersonalEventUser(PersonalEvent personalEvent, User user) {
Expand All @@ -240,37 +279,15 @@ private void validatePersonalEventUser(PersonalEvent personalEvent, User user) {
}
}

private void upsertPersonalEventByGoogleEvent(
GoogleEvent googleEvent, List<Discussion> discussions, User user, String googleCalendarId) {
log.info("Upserting personal event by Google event: {}", googleEvent);

personalEventRepository
.findByGoogleEventIdAndCalendarId(googleEvent.eventId(), googleCalendarId)
.ifPresentOrElse(personalEvent -> {
updatePersonalEvent(
PersonalEventRequest.of(googleEvent, personalEvent.getIsAdjustable()),
personalEvent, user, discussions);
},
() -> {
PersonalEvent personalEvent =
PersonalEvent.fromGoogleEvent(googleEvent, user, googleCalendarId);
personalEventRepository.save(personalEvent);
// 비트맵 수정
discussions.forEach(discussion -> {
personalEventPreprocessor.preprocessOne(personalEvent, discussion, user,
true);
});
});
}

private void deletePersonalEventByGoogleEvent(GoogleEvent googleEvent,
private Optional<PersonalEvent> deletePersonalEventByGoogleEvent(GoogleEvent googleEvent,
List<Discussion> discussions, User user, String googleCalendarId) {
log.info("Deleting personal event by Google event: {}", googleEvent);
personalEventRepository
.findByGoogleEventIdAndCalendarId(googleEvent.eventId(), googleCalendarId)
.ifPresent(personalEvent -> {
deletePersonalEvent(personalEvent, user, discussions);
});
Optional<PersonalEvent> opt = personalEventRepository
.findByGoogleEventIdAndCalendarId(googleEvent.eventId(), googleCalendarId);

opt.ifPresent(personalEvent -> deletePersonalEvent(personalEvent, user, discussions));

return opt;
}

private boolean isDateTimeChanged(PersonalEvent personalEvent, PersonalEventRequest newEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ public record PersonalEventResponse(
LocalDateTime startDateTime,
LocalDateTime endDateTime,
Boolean isAdjustable,
String calendarId,
String googleEventId
String calendarId
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Appropriate removal of Google-specific field.

Removing the googleEventId field from the public API response is a good architectural decision that reduces coupling with Google Calendar implementation details.

Consider if this removal constitutes a breaking change for existing API consumers. Please verify if there are any client applications that depend on this field.


🏁 Script executed:

#!/bin/bash
# Description: Search for usage of googleEventId in client code or documentation
# Expected: Should identify any dependencies on this field

rg -i "googleeventid" --type-add 'docs:*.md,*.txt,*.rst' --type docs
rg -i "google.*event.*id" --type-add 'frontend:*.js,*.ts,*.jsx,*.tsx' --type frontend

Length of output: 454


I’m not finding any occurrences because the previous ripgrep commands were overly restrictive. Let’s search the entire repo for both camelCase and snake_case variants:


🏁 Script executed:

#!/bin/bash
# Search for any references to googleEventId or google_event_id across all files
rg -i "googleEventId" -n .
rg -i "google_event_id" -n .

Length of output: 6031


Breaking Change: Update Front-end Model After Removing googleEventId

The API DTO no longer includes googleEventId, but your front-end schema still expects it—this will break existing clients. Please remove or adapt all references to googleEventId in consumer code:

• frontend/src/features/my-calendar/model/index.ts
– Remove the googleEventId: z.string(), field (line 10)
– Remove googleEventId: true from the selected properties object (line 16)

After updating the front-end, verify end-to-end functionality (e.g., calendar listing, event details) to ensure no runtime errors occur when the field is absent.

🤖 Prompt for AI Agents
In
backend/src/main/java/endolphin/backend/domain/personal_event/dto/PersonalEventResponse.java
at line 12, the googleEventId field was removed, causing a breaking change for
front-end clients. To fix this, update the front-end model by removing the
googleEventId field declaration at line 10 and removing googleEventId: true from
the selected properties object at line 16 in
frontend/src/features/my-calendar/model/index.ts. After these changes, test the
full flow to ensure no runtime errors occur due to the missing field.

) {

public static PersonalEventResponse fromEntity(PersonalEvent event) {
Expand All @@ -20,8 +19,7 @@ public static PersonalEventResponse fromEntity(PersonalEvent event) {
event.getStartTime(),
event.getEndTime(),
event.getIsAdjustable(),
event.getCalendarId(),
event.getGoogleEventId()
event.getCalendarId()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package endolphin.backend.domain.personal_event.dto;

import endolphin.backend.domain.personal_event.entity.PersonalEvent;
import endolphin.backend.global.google.dto.GoogleEvent;
import endolphin.backend.global.google.enums.GoogleEventStatus;
import java.time.LocalDateTime;

public record SyncPersonalEvent(
String googleEventId,
Long id,
Boolean isAdjustable,
String calendarId,
String title,
LocalDateTime startDateTime,
LocalDateTime endDateTime,
String status
Status status
) {
public static SyncPersonalEvent from(GoogleEvent event) {
return new SyncPersonalEvent(event.eventId(), event.summary(), event.startDateTime(),
event.endDateTime(), event.status().getValue());

Copy link
Collaborator

Choose a reason for hiding this comment

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

just ask; status를 enum 말고 String으로 했던 이유가 있었던가요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

db를 거쳐도 googleEventId 응답에 포함해야 하나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • 가독성을 위해 enum으로 수정하겠습니다!
  • 이전 업데이트 때 일정들을 googleEventId로 구분하도록 프론트에서 반영했을거라 googleEventId를 응답에서 빼는 건 프론트와 협의가 필요할 거 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

프론트와 협의한 결과, googleEventId를 응답에서 삭제하기로 결정하였습니다.!

public static SyncPersonalEvent from(PersonalEvent event, Status status) {
return new SyncPersonalEvent(event.getId(), event.getIsAdjustable(), event.getCalendarId(),
event.getTitle(), event.getStartTime(), event.getEndTime(), status);
}

public enum Status {
CREATED, UPDATED, DELETED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,4 @@ public PersonalEvent copy() {
.user(this.user)
.build();
}

public static PersonalEvent fromGoogleEvent(GoogleEvent googleEvent, User user,
String googleCalenderId) {
return PersonalEvent.builder()
.title(googleEvent.summary())
.startTime(googleEvent.startDateTime())
.endTime(googleEvent.endDateTime())
.googleEventId(googleEvent.eventId())
.isAdjustable(false)
.calendarId(googleCalenderId)
.user(user)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ public void sync(GoogleEventChanged event) {
log.info("Syncing personal events with Google events");
try {
List<GoogleEvent> events = event.events();
String googleCalendarId = event.googleCalendarId();
User user = event.user();

log.info("Syncing personal events for user {}", user.getId());
String googleCalendarId = event.googleCalendarId();
personalEventService.syncWithGoogleEvents(events, user, googleCalendarId);

List<SyncPersonalEvent> syncPersonalEvents =
personalEventService.syncWithGoogleEvents(events, user, googleCalendarId);

if (deferredResultManager.hasActiveConnection(user)) {
List<SyncPersonalEvent> syncPersonalEvents =
events.stream().map(SyncPersonalEvent::from).toList();
deferredResultManager.setResult(user, SyncResponse.sync(syncPersonalEvents));
}


} catch (Exception e) {
log.error("Failed to sync personal events", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

import endolphin.backend.domain.personal_event.dto.PersonalEventResponse;
import endolphin.backend.domain.personal_event.entity.PersonalEvent;
import java.time.LocalDateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
Expand Down Expand Up @@ -47,11 +49,14 @@ void createPersonalEvent() throws Exception {
"isAdjustable": false
}
""";
PersonalEvent personalEvent = mock(PersonalEvent.class);
given(personalEvent.getId()).willReturn(1L);
PersonalEventResponse personalEventResponse = new PersonalEventResponse(1L, "title",
LocalDateTime.of(2025, 2, 2, 10, 0),
LocalDateTime.of(2025, 2, 2, 12, 0),
false, "testCalendarId", "testGoogleEventId");
given(personalEventService.createWithRequest(any())).willReturn(personalEventResponse);
false, "testCalendarId");
given(personalEventService.createWithRequest(any())).willReturn(personalEvent);

MvcResult result = mockMvc.perform(post("/api/v1/personal-event").
contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ void testCreateWithRequest() {
given(personalEventRepository.save(any(PersonalEvent.class))).willReturn(savedEvent);

// when
PersonalEventResponse response = personalEventService.createWithRequest(request);
PersonalEvent response = personalEventService.createWithRequest(request);

// then
assertThat(response).isNotNull();
assertThat(response.title()).isEqualTo(request.title());
assertThat(response.startDateTime()).isEqualTo(startTime);
assertThat(response.getTitle()).isEqualTo(request.title());
assertThat(response.getStartTime()).isEqualTo(startTime);

verify(userService, times(1)).getCurrentUser();
verify(personalEventRepository, times(1)).save(any(PersonalEvent.class));
Expand Down Expand Up @@ -233,14 +233,20 @@ public void testUpdateWithRequestByGoogleSync_Success() {

PersonalEvent existingEvent = createWithRequest("new Title");
given(existingEvent.getStartTime()).willReturn(LocalDateTime.of(2024, 3, 10, 10, 0));
// given(existingEvent.getEndTime()).willReturn(LocalDateTime.of(2024, 3, 10, 12, 0));

PersonalEvent oldExistingEvent = createWithRequest("Old Title");
given(existingEvent.copy()).willReturn(oldExistingEvent);

PersonalEvent updatedPersonalEvent = createWithRequest("new Title");
given(updatedPersonalEvent.getId()).willReturn(1L);
given(updatedPersonalEvent.getTitle()).willReturn("new Title");
given(updatedPersonalEvent.getStartTime()).willReturn(LocalDateTime.of(2024, 3, 10, 10, 0));
given(updatedPersonalEvent.getEndTime()).willReturn(LocalDateTime.of(2024, 3, 10, 12, 0));
given(updatedPersonalEvent.getCalendarId()).willReturn("testCalendarId");
given(updatedPersonalEvent.getIsAdjustable()).willReturn(false);
given(personalEventRepository.save(any())).willReturn(updatedPersonalEvent);

PersonalEvent existingEvent2 = createWithRequest("Old Title2");
// given(existingEvent2.getStartTime()).willReturn(LocalDateTime.of(2024, 5, 10, 7, 0));
// given(existingEvent2.getEndTime()).willReturn(LocalDateTime.of(2024, 5, 10, 12, 0));

given(personalEventRepository.findByGoogleEventIdAndCalendarId(eq(updatedGoogleEvent.eventId()), eq(googleCalendarId)))
.willReturn(Optional.of(existingEvent));
Expand Down
Loading