diff --git a/src/main/java/com/vi/appointmentservice/api/calcom/repository/EventTypeRepository.java b/src/main/java/com/vi/appointmentservice/api/calcom/repository/EventTypeRepository.java index 17e9622..21dea40 100644 --- a/src/main/java/com/vi/appointmentservice/api/calcom/repository/EventTypeRepository.java +++ b/src/main/java/com/vi/appointmentservice/api/calcom/repository/EventTypeRepository.java @@ -3,6 +3,7 @@ import com.vi.appointmentservice.api.calcom.model.CalcomEventType; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import javax.sql.DataSource; import javax.validation.constraints.NotNull; @@ -39,6 +40,17 @@ public CalcomEventType getEventTypeById(Number eventTypeId) { return getCalcomEventType(result); } + public Optional findEventTypeById(Number eventTypeId) { + String selectEvent = "SELECT * FROM \"EventType\" WHERE id = :eventTypeId"; + SqlParameterSource parameters = new MapSqlParameterSource(EVENT_TYPE_ID, eventTypeId); + List> result = db.queryForList(selectEvent, parameters); + if (result.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(getCalcomEventType(result.get(0))); + } + } + public CalcomEventType getEventTypeByUserId(Number userId) { String selectEvent = "SELECT * FROM \"EventType\" WHERE \"userId\" = :userId"; SqlParameterSource parameters = new MapSqlParameterSource(USER_ID, userId); diff --git a/src/main/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepository.java b/src/main/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepository.java index cfea888..e7faed3 100644 --- a/src/main/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepository.java +++ b/src/main/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepository.java @@ -1,9 +1,23 @@ package com.vi.appointmentservice.api.calcom.repository; +import static org.openapitools.codegen.meta.features.DataTypeFeature.Maps; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import javax.validation.constraints.NotNull; import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -11,6 +25,7 @@ import org.springframework.stereotype.Repository; @Repository +@Slf4j public class ScheduleRepository { private final @NotNull JdbcTemplate jdbcTemplate; @@ -29,12 +44,40 @@ public ScheduleRepository(@Qualifier("dbTemplate") NamedParameterJdbcTemplate db this.jdbcTemplate = jdbcTemplate; } - public List deleteUserSchedules(Long calcomUserId) { + public Set deleteUserSchedules(Long calcomUserId) { + var originalScheduleIds = getScheduleIdsByUserId(db, calcomUserId); String DELETE_SCHEDULE = "DELETE FROM \"Schedule\" where \"userId\" = :userId"; SqlParameterSource parameters = new MapSqlParameterSource("userId", calcomUserId); db.update(DELETE_SCHEDULE, parameters); - //TODO: return ids of removed schedules - return null; + var leftScheduleIds = getScheduleIdsByUserId(db, calcomUserId); + return Sets.difference(originalScheduleIds, leftScheduleIds); + } + + public List getTableNames(JdbcTemplate jdbcTemplate) throws SQLException { + List tableNames = new ArrayList<>(); + Connection connection = jdbcTemplate.getDataSource().getConnection(); + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet rs = metaData.getTables(null, null, "%", null); + while (rs.next()) { + tableNames.add(rs.getString("TABLE_NAME")); + } + return tableNames; + } + public Set getScheduleIdsByUserId(NamedParameterJdbcTemplate jdbcTemplate, Long userId) { + Set scheduleIds = Sets.newHashSet(); + Map params = new HashMap<>(); + params.put("userId", userId); + String sql = "SELECT \"id\" FROM \"Schedule\" WHERE \"userId\" = :userId"; + try { + List> rows = jdbcTemplate.queryForList(sql, params); + for (Map row : rows) { + scheduleIds.add((Integer) row.get("id")); + } + } catch (DataAccessException e) { + log.error("Error while fetching schedule ids for user: {}", userId, e); + } + + return scheduleIds; } public Long createDefaultSchedule(Long calcomUserId) { diff --git a/src/main/java/com/vi/appointmentservice/api/calcom/service/CalcomEventTypeService.java b/src/main/java/com/vi/appointmentservice/api/calcom/service/CalcomEventTypeService.java index f495a14..92412ce 100644 --- a/src/main/java/com/vi/appointmentservice/api/calcom/service/CalcomEventTypeService.java +++ b/src/main/java/com/vi/appointmentservice/api/calcom/service/CalcomEventTypeService.java @@ -6,6 +6,7 @@ import com.vi.appointmentservice.api.calcom.repository.MembershipsRepository; import com.vi.appointmentservice.api.calcom.repository.WebhookRepository; import com.vi.appointmentservice.api.facade.AppointmentType; +import com.vi.appointmentservice.api.facade.DefaultTextConstants; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -42,6 +43,10 @@ public CalcomEventType getEventTypeById(Number eventTypeId) { return eventType; } + public Optional findEventTypeById(Number eventTypeId) { + return eventTypeRepository.findEventTypeById(eventTypeId); + } + public CalcomEventType getEventTypeByUserId(Number userId) { return eventTypeRepository.getEventTypeByUserId(userId); } @@ -173,4 +178,14 @@ public void deleteEventType(Long eventTypeId) { eventTypeRepository.removeTeamEventTypeMembershipsForEventType(eventTypeId); eventTypeRepository.removeTeamEventHostsForEventType(eventTypeId); } + + public void updateEventTypeTitle(Long calComUserId, String displayName) { + CalcomEventType eventTypeByUserId = getEventTypeByUserId(calComUserId); + if (eventTypeByUserId.getTitle().contains(DefaultTextConstants.BERATUNG_MIT)) { + eventTypeByUserId.setTitle(DefaultTextConstants.BERATUNG_MIT_DEM_DER_BERATER_IN + " " + displayName); + eventTypeRepository.updateEventType(eventTypeByUserId); + } else { + log.warn("Skipping update of EventType because event type for the user {} does not contain text {}", calComUserId, DefaultTextConstants.BERATUNG_MIT_DEM_DER_BERATER_IN); + } + } } diff --git a/src/main/java/com/vi/appointmentservice/api/facade/ConsultantFacade.java b/src/main/java/com/vi/appointmentservice/api/facade/ConsultantFacade.java index 74212ec..1344c22 100644 --- a/src/main/java/com/vi/appointmentservice/api/facade/ConsultantFacade.java +++ b/src/main/java/com/vi/appointmentservice/api/facade/ConsultantFacade.java @@ -1,5 +1,7 @@ package com.vi.appointmentservice.api.facade; +import com.google.common.collect.Lists; +import com.vi.appointmentservice.api.calcom.model.CalcomEventType; import com.vi.appointmentservice.api.calcom.model.CalcomUser; import com.vi.appointmentservice.api.calcom.repository.AvailabilityRepository; import com.vi.appointmentservice.api.calcom.repository.BookingRepository; @@ -21,11 +23,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -76,8 +78,11 @@ public void patchAppointmentUser(String consultantId, PatchConsultantDTO consult var name = getDisplayNameOrFallbackToFirstname(consultant); Optional userConsultant = userToConsultantRepository .findByConsultantId(consultantId); + Long calComUserId = userConsultant.orElseThrow().getCalComUserId(); calComUserService - .updateUsername(userConsultant.orElseThrow().getCalComUserId(), name); + .updateUsername(calComUserId, name); + + calComEventTypeService.updateEventTypeTitle(calComUserId, name); } private void linkConsultantToAppointmentUser( @@ -90,7 +95,7 @@ private void linkConsultantToAppointmentUser( void setupDefaultScheduleAndEventType(CalcomUser calcomUser) { Long defaultScheduleId = scheduleRepository.createDefaultSchedule(calcomUser.getId()); AppointmentType defaultAppointmentType = appointmentService.createDefaultAppointmentType(); - defaultAppointmentType.setTitle("Beratung mit dem / der Berater:in"); + defaultAppointmentType.setTitle(DefaultTextConstants.BERATUNG_MIT_DEM_DER_BERATER_IN); calComEventTypeService .createEventType(calcomUser, defaultAppointmentType, defaultScheduleId); @@ -105,7 +110,7 @@ public void deleteConsultantHandler(String consultantId) { // Delete personal event-types calComEventTypeService.deleteAllEventTypesOfUser(calcomUserId); // Delete schedules - List deletedSchedules = scheduleRepository.deleteUserSchedules(calcomUserId); + Set deletedSchedules = scheduleRepository.deleteUserSchedules(calcomUserId); // Delete availabilities for schedules for (Integer scheduleId : deletedSchedules) { availabilityRepository.deleteAvailabilityByScheduleId(Long.valueOf(scheduleId)); diff --git a/src/main/java/com/vi/appointmentservice/api/facade/DefaultTextConstants.java b/src/main/java/com/vi/appointmentservice/api/facade/DefaultTextConstants.java new file mode 100644 index 0000000..ca58b22 --- /dev/null +++ b/src/main/java/com/vi/appointmentservice/api/facade/DefaultTextConstants.java @@ -0,0 +1,11 @@ +package com.vi.appointmentservice.api.facade; + +public class DefaultTextConstants { + + private DefaultTextConstants() { + // private constructor to hide the implicit public one + } + + public static final String BERATUNG_MIT = "Beratung mit "; + public static final String BERATUNG_MIT_DEM_DER_BERATER_IN = "Beratung mit dem / der Berater:in"; +} diff --git a/src/main/java/com/vi/appointmentservice/helper/RescheduleHelper.java b/src/main/java/com/vi/appointmentservice/helper/RescheduleHelper.java index 015da75..f8f715b 100644 --- a/src/main/java/com/vi/appointmentservice/helper/RescheduleHelper.java +++ b/src/main/java/com/vi/appointmentservice/helper/RescheduleHelper.java @@ -38,25 +38,38 @@ public CalcomBooking attachRescheduleLink(CalcomBooking calcomBooking) { .getUserById(Long.valueOf(calcomBooking.getUserId())); var teamId = getTeamIdForBooking(calcomBooking); String slug = null; - if (teamId != null) { - CalcomTeam team = calComTeamService.getTeamById(teamId); + if (teamId.isPresent()) { + CalcomTeam team = calComTeamService.getTeamById(teamId.get()); slug = "team/" + team.getSlug(); } else { slug = registeredCalcomUser.getUsername(); } - String eventTypeSlug = this.calcomEventTypeService.getEventTypeById( - Long.valueOf(calcomBooking.getEventTypeId())).getSlug(); - calcomBooking.setRescheduleLink( - "/" + slug + "/" + eventTypeSlug + "?rescheduleUid=" + calcomBooking.getUid()); + return attachRescheduleLinkIfEventTypeIsFound(calcomBooking, slug); + } + + private CalcomBooking attachRescheduleLinkIfEventTypeIsFound(CalcomBooking calcomBooking, String slug) { + var optionalEventType = this.calcomEventTypeService.findEventTypeById( + Long.valueOf(calcomBooking.getEventTypeId())); + if (optionalEventType.isEmpty()) { + log.warn("EventType not found for bookingId " + calcomBooking.getId()); + return calcomBooking; + } else { + calcomBooking.setRescheduleLink( + "/" + slug + "/" + optionalEventType.get().getSlug() + "?rescheduleUid=" + calcomBooking.getUid()); + } return calcomBooking; } - private Number getTeamIdForBooking(CalcomBooking calcomBooking) { - CalcomEventType eventType = calcomEventTypeService - .getEventTypeById(Long.valueOf(calcomBooking.getEventTypeId())); - return eventType.getTeamId(); + private Optional getTeamIdForBooking(CalcomBooking calcomBooking) { + Optional eventType = calcomEventTypeService + .findEventTypeById(Long.valueOf(calcomBooking.getEventTypeId())); + if (eventType.isEmpty() || eventType.get().getTeamId() == null) { + return Optional.empty(); + } else { + return Optional.of(eventType.get().getTeamId()); + } } public void attachConsultantName(List bookings) { diff --git a/src/main/resources/application-testing.properties b/src/main/resources/application-testing.properties index f56ed9c..4e082d8 100644 --- a/src/main/resources/application-testing.properties +++ b/src/main/resources/application-testing.properties @@ -22,7 +22,7 @@ keycloak.config.app-client-id=app-ci spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 spring.sql.init.schema-locations=classpath*:database/AppointmentServiceDatabase.sql -calcom.database.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +calcom.database.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;MODE=PostgreSQL calcom.database.username=appointmentservice calcom.database.password=appointmentservice calcom.database.driverClass=org.h2.Driver diff --git a/src/test/java/com.vi.appointmentservice.api.service.calcom/CalcomEventTypeServiceTest.java b/src/test/java/com.vi.appointmentservice.api.service.calcom/CalcomEventTypeServiceTest.java index d4e245c..6a88987 100644 --- a/src/test/java/com.vi.appointmentservice.api.service.calcom/CalcomEventTypeServiceTest.java +++ b/src/test/java/com.vi.appointmentservice.api.service.calcom/CalcomEventTypeServiceTest.java @@ -1,5 +1,6 @@ package com.vi.appointmentservice.api.service.calcom; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,6 +13,7 @@ import org.assertj.core.util.Lists; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -63,4 +65,31 @@ public void shouldCreateEventTypeAndNotUpdateLocationsIfAppointmentTypeDoesNotCo Mockito.verify(eventTypeRepository, Mockito.never()).updateLocations(Mockito.anyInt(), Mockito.anyString()); } + @Test + public void shouldUpdateEventTypeTitle() { + // given + var eventType = new CalcomEventType(); + eventType.setId(1); + eventType.setTitle("Beratung mit dem / der Berater:in ConsultantFirstname"); + + Long calcomUserId = 2L; + when(eventTypeRepository.getEventTypeByUserId(calcomUserId)).thenReturn(eventType); + + // when + calcomEventTypeService.updateEventTypeTitle(calcomUserId, "ConsultantDisplayName"); + // then + + ArgumentCaptor captor = ArgumentCaptor.forClass(CalcomEventType.class); + Mockito.verify(eventTypeRepository).updateEventType(captor.capture()); + assertThat(captor.getValue().getTitle()).isEqualTo("Beratung mit dem / der Berater:in ConsultantDisplayName"); + } + + @Test + public void shouldDelegateToRepositoryToFindEventType() { + // when + calcomEventTypeService.findEventTypeById(1L); + // then + Mockito.verify(eventTypeRepository).findEventTypeById(1L); + } + } \ No newline at end of file diff --git a/src/test/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepositoryTest.java b/src/test/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepositoryTest.java new file mode 100644 index 0000000..1856ef8 --- /dev/null +++ b/src/test/java/com/vi/appointmentservice/api/calcom/repository/ScheduleRepositoryTest.java @@ -0,0 +1,59 @@ +package com.vi.appointmentservice.api.calcom.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; +import org.junit.Before; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@TestPropertySource(properties = "spring.profiles.active=testing") +@AutoConfigureTestDatabase(replace = Replace.ANY) +@ExtendWith(SpringExtension.class) +@SpringBootTest +class ScheduleRepositoryTest { + + @Autowired + ScheduleRepository scheduleRepository; + + @Autowired + JdbcTemplate jdbcTemplate; + + @Before + public void setUp() { + jdbcTemplate.execute("DROP TABLE IF EXISTS \"Schedule\""); + } + @Test + void deleteUserSchedules_Should_DeleteSchedulesPerUserId() { + // given + inititalizeDB(); + + jdbcTemplate.execute("INSERT INTO \"Schedule\" (\"id\", \"userId\", \"name\") VALUES (1, 1, 'DEFAULT_SCHEDULE')"); + jdbcTemplate.execute("INSERT INTO \"Schedule\" (\"id\", \"userId\", \"name\") VALUES (2, 1, 'DEFAULT_SCHEDULE')"); + // when + Set integers = scheduleRepository.deleteUserSchedules(1L); + // then + assertThat(integers).containsOnly(1, 2); + + } + + private void inititalizeDB() { + // we can't use @Sql annotation here because it's not visible in jdbcTemplate, + // probably because there are defined multiple jdbc templates in this project for different datasources + jdbcTemplate.execute("create table \"Schedule\"\n" + + "(\n" + + " \"id\" integer not null\n" + + " primary key,\n" + + " \"userId\" integer not null,\n" + + " \"name\" varchar(255) not null\n" + + ");"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/vi/appointmentservice/helper/RescheduleHelperTest.java b/src/test/java/com/vi/appointmentservice/helper/RescheduleHelperTest.java new file mode 100644 index 0000000..551a41b --- /dev/null +++ b/src/test/java/com/vi/appointmentservice/helper/RescheduleHelperTest.java @@ -0,0 +1,75 @@ +package com.vi.appointmentservice.helper; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +import com.vi.appointmentservice.api.calcom.model.CalcomEventType; +import com.vi.appointmentservice.api.calcom.model.CalcomUser; +import com.vi.appointmentservice.api.calcom.repository.EventTypeRepository; +import com.vi.appointmentservice.api.calcom.service.CalComTeamService; +import com.vi.appointmentservice.api.calcom.service.CalComUserService; +import com.vi.appointmentservice.api.calcom.service.CalcomEventTypeService; +import com.vi.appointmentservice.api.model.CalcomBooking; +import com.vi.appointmentservice.api.service.onlineberatung.AdminUserService; +import com.vi.appointmentservice.repository.CalcomBookingToAskerRepository; +import com.vi.appointmentservice.repository.UserToConsultantRepository; +import java.util.Optional; +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; + +@ExtendWith(MockitoExtension.class) +class RescheduleHelperTest { + + @InjectMocks + RescheduleHelper rescheduleHelper; + + @Mock + CalComUserService calComUserService; + + @Mock + EventTypeRepository eventTypeRepository; + + @Mock + UserToConsultantRepository userToConsultantRepository; + + @Mock + AdminUserService adminUserService; + + @Mock + CalcomBookingToAskerRepository calcomBookingToAskerRepository; + + @Mock + CalComTeamService calComTeamService; + + @Mock + CalcomEventTypeService calcomEventTypeService; + + @Test + void shouldNotAttachRescheduleLink_When_EventTypeIsNotFound() { + // given + when(calComUserService.getUserById(Mockito.any())).thenReturn(new CalcomUser()); + + when(calcomEventTypeService.findEventTypeById(Mockito.any())).thenReturn(Optional.empty()); + // when + var result = rescheduleHelper.attachRescheduleLink(new CalcomBooking().userId(1).eventTypeId(2)); + // then + assertNull(result.getRescheduleLink()); + } + + @Test + void should_AttachRescheduleLink_When_EventTypeIsFound() { + // given + when(calComUserService.getUserById(Mockito.any())).thenReturn(new CalcomUser()); + CalcomEventType calcomEventType = new CalcomEventType(); + calcomEventType.setSlug("slug"); + when(calcomEventTypeService.findEventTypeById(Mockito.any())).thenReturn(Optional.of(calcomEventType)); + // when + var result = rescheduleHelper.attachRescheduleLink(new CalcomBooking().userId(1).eventTypeId(2)); + // then + assertNotNull(result.getRescheduleLink()); + } +} \ No newline at end of file