From 255bead0317f745d5be5d8310515e1dadb25924a Mon Sep 17 00:00:00 2001 From: Daniil Zavyalov Date: Mon, 4 Mar 2024 00:15:54 +0600 Subject: [PATCH 1/5] [~] requests to workspace calendars --- .../repository/BookingCalendarRepository.kt | 135 ++++++++++-------- .../booking/service/BookingService.kt | 2 +- .../repository/CalendarIdsRepository.kt | 13 +- .../src/main/resources/application.conf | 2 +- 4 files changed, 81 insertions(+), 71 deletions(-) diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt index 3144c96cc..c46efcd1e 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt @@ -4,11 +4,11 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException import com.google.api.client.util.DateTime import com.google.api.services.calendar.Calendar import com.google.api.services.calendar.model.Event +import kotlinx.coroutines.* import office.effective.common.constants.BookingConstants import office.effective.common.exception.InstanceNotFoundException import office.effective.common.exception.MissingIdException import office.effective.common.exception.WorkspaceUnavailableException -import office.effective.config import office.effective.features.calendar.repository.CalendarIdsRepository import office.effective.features.booking.converters.GoogleCalendarConverter import office.effective.features.user.repository.UserRepository @@ -17,6 +17,8 @@ import office.effective.features.user.repository.UserEntity import org.slf4j.LoggerFactory import java.time.Instant import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors /** * Class that executes Google calendar queries for booking meeting rooms @@ -38,7 +40,6 @@ class BookingCalendarRepository( * * @param workspaceId * @return calendar id by workspace id in database - * @author Danil Kiselev, Daniil Zavyalov */ private fun getCalendarIdByWorkspace(workspaceId: UUID): String { return try { @@ -53,7 +54,6 @@ class BookingCalendarRepository( * * @param id booking id * @return true if booking exists - * @author Danil Kiselev */ override fun existsById(id: String): Boolean { logger.debug("[existsById] checking whether a booking with id={} exists", id) @@ -66,7 +66,6 @@ class BookingCalendarRepository( * Deletes the booking with the given id * * @param id booking id - * @author Danil Kiselev */ override fun deleteById(id: String) { logger.debug("[deleteById] deleting the booking with id={}", id) @@ -85,7 +84,6 @@ class BookingCalendarRepository( * * @param bookingId booking id * @return [Booking] with the given [bookingId] or null if booking with the given id doesn't exist - * @author Daniil Zavyalov, Danil Kiselev */ override fun findById(bookingId: String): Booking? { logger.debug("[findById] retrieving a booking with id={}", bookingId) @@ -101,7 +99,6 @@ class BookingCalendarRepository( * @param calendarId the calendar in which to search for the event * @return [Event] with the given [bookingId] from calendar with [calendarId] * or null if event with the given id doesn't exist - * @author Danil Kiselev, Daniil Zavyalov */ private fun findByCalendarIdAndBookingId(bookingId: String, calendarId: String = defaultCalendar): Event? { logger.trace("Retrieving event from {} calendar by id", calendarId) @@ -121,7 +118,6 @@ class BookingCalendarRepository( * @param timeMax * @param singleEvents * @param calendarId - * @author Daniil Zavyalov */ private fun basicQuery( timeMin: Long, @@ -135,22 +131,6 @@ class BookingCalendarRepository( .setTimeMax(timeMax?.let { DateTime(it) }) } - /** - * Checks whether the event contains workspace with the given calendar id - * (Rooms (workspaces) are also attendees of event). - * - * @param event - * @param attendeeEmail - * @return List of all workspace [Event]s - * @author Danil Kiselev, Daniil Zavyalov - */ - private fun hasAttendee(event: Event, attendeeEmail: String): Boolean { - event.attendees?.forEach { - if (it.email == attendeeEmail) return true - } - return false - } - /** * Returns all bookings with the given workspace id * @@ -159,7 +139,6 @@ class BookingCalendarRepository( * Old Google calendar events may not appear correctly in the system and cause unexpected exceptions * @param eventRangeTo upper bound (exclusive) for a beginBooking to filter by. Optional. * @return List of all workspace [Booking] - * @author Daniil Zavyalov, Danil Kiselev */ override fun findAllByWorkspaceId(workspaceId: UUID, eventRangeFrom: Long, eventRangeTo: Long?): List { logger.debug( @@ -169,19 +148,10 @@ class BookingCalendarRepository( eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity" ) val workspaceCalendarId = getCalendarIdByWorkspace(workspaceId) - val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo) - .setQ(workspaceCalendarId) + val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo, true, workspaceCalendarId) .execute().items - val result: MutableList = mutableListOf() - eventsWithWorkspace?.forEach { event -> - if (hasAttendee(event, workspaceCalendarId)) { - result.add(googleCalendarConverter.toBookingModel(event)) - } else { - logger.trace("[findAllByOwnerId] filtered out event: {}", event) - } - } - return result + return eventsWithWorkspace.toList().map { googleCalendarConverter.toBookingModel(it) } } /** @@ -215,6 +185,66 @@ class BookingCalendarRepository( ) } + private fun getAllEvents2( + calendarIds: List, + eventRangeFrom: Long, + eventRangeTo: Long? + ): List = runBlocking { + val res = calendarIds.map { calendarId -> + async { + basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) + .execute().items + } + } + res.awaitAll().flatten() + } + + private fun getEventsWithQParam( + calendarIds: List, + q: String, + eventRangeFrom: Long, + eventRangeTo: Long? + ): List { + val executor = Executors.newFixedThreadPool(calendarIds.size) + val futures = calendarIds.map { calendarId -> + CompletableFuture.supplyAsync { + basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) + .setQ(q) + .execute().items + } + } + val allEvents = CompletableFuture.allOf(*futures.toTypedArray()) + .thenApply { + futures.map { it.get() } + } + .join() + executor.shutdown() + + return allEvents.flatten() + } + + private fun getAllEvents( + calendarIds: List, + eventRangeFrom: Long, + eventRangeTo: Long? + ): List { + val executor = Executors.newFixedThreadPool(calendarIds.size) + val futures = calendarIds.map { calendarId -> + CompletableFuture.supplyAsync { + basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) + .execute().items + } + } + val allEvents = CompletableFuture.allOf(*futures.toTypedArray()) + .thenApply { + futures.map { it.get() } + } + .join() + executor.shutdown() + + return allEvents.flatten() + } + /** * Returns all bookings with the given owner id * @@ -234,20 +264,18 @@ class BookingCalendarRepository( eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity" ) val userEmail: String = findUserEmailByUserId(ownerId) + val calendars: List = calendarIdsRepository.findAllCalendarsId() - val eventsWithUser = basicQuery(eventRangeFrom, eventRangeTo) - .setQ(userEmail) - .execute().items + val eventsWithUser = getEventsWithQParam(calendars, userEmail, eventRangeFrom, eventRangeTo) val result = mutableListOf() - eventsWithUser.forEach { event -> + for (event in eventsWithUser) { if (checkEventOrganizer(event, userEmail)) { result.add(googleCalendarConverter.toBookingModel(event)) } else { logger.trace("[findAllByOwnerId] filtered out event: {}", event) } } - return result } @@ -260,7 +288,6 @@ class BookingCalendarRepository( * Old Google calendar events may not appear correctly in the system and cause unexpected exceptions * @param eventRangeTo upper bound (exclusive) for a beginBooking to filter by. Optional. * @return List of all [Booking]s with the given workspace and owner id - * @author Daniil Zavyalov */ override fun findAllByOwnerAndWorkspaceId( ownerId: UUID, @@ -278,16 +305,16 @@ class BookingCalendarRepository( val userEmail: String = findUserEmailByUserId(ownerId) val workspaceCalendarId = getCalendarIdByWorkspace(workspaceId) - val eventsWithUserAndWorkspace = basicQuery(eventRangeFrom, eventRangeTo) - .setQ("$userEmail $workspaceCalendarId") + val eventsWithUserAndWorkspace = basicQuery(eventRangeFrom, eventRangeTo, true, workspaceCalendarId) + .setQ(userEmail) .execute().items val result = mutableListOf() - eventsWithUserAndWorkspace.forEach { event -> - if (checkEventOrganizer(event, userEmail) && hasAttendee(event, workspaceCalendarId)) { + for (event in eventsWithUserAndWorkspace) { + if (checkEventOrganizer(event, userEmail)) { result.add(googleCalendarConverter.toBookingModel(event)) } else { - logger.trace("[findAllByOwnerId] filtered out event: {}", event) + logger.trace("[findAllByOwnerAndWorkspaceId] filtered out event: {}", event) } } @@ -301,7 +328,6 @@ class BookingCalendarRepository( * Old Google calendar events may not appear correctly in the system and cause unexpected exceptions * @param eventRangeTo upper bound (exclusive) for a beginBooking to filter by. Optional. * @return All [Booking]s - * @author Daniil Zavyalov */ override fun findAll(eventRangeFrom: Long, eventRangeTo: Long?): List { logger.debug( @@ -309,9 +335,9 @@ class BookingCalendarRepository( Instant.ofEpochMilli(eventRangeFrom), eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity" ) - return basicQuery(eventRangeFrom, eventRangeTo).execute().items.map { event -> - googleCalendarConverter.toBookingModel(event) - } + val calendars: List = calendarIdsRepository.findAllCalendarsId() + val events: List = getAllEvents(calendars, eventRangeFrom, eventRangeTo) + return events.map { googleCalendarConverter.toBookingModel(it) } } /** @@ -320,7 +346,6 @@ class BookingCalendarRepository( * * @param booking [Booking] to be saved * @return saved [Booking] - * @author Danil Kiselev */ override fun save(booking: Booking): Booking { logger.debug("[save] saving booking of workspace with id {}", booking.workspace.id) @@ -351,7 +376,6 @@ class BookingCalendarRepository( * @throws MissingIdException if [Booking.id] or [Booking.workspace].id is null * @throws InstanceNotFoundException if booking given id doesn't exist in the database * @throws WorkspaceUnavailableException if booking unavailable because of collision check - * @author Daniil Zavyalov, Danil Kiselev */ override fun update(booking: Booking): Booking { logger.debug("[update] updating booking of workspace with id {}", booking.id) @@ -387,7 +411,6 @@ class BookingCalendarRepository( * * @param incomingEvent: [Event] - Must take only SAVED event * @return Boolean. True if booking available - * @author Kiselev Danil * */ private fun checkBookingAvailable(incomingEvent: Event, workspaceCalendar: String): Boolean { logger.debug( @@ -399,7 +422,7 @@ class BookingCalendarRepository( if (incomingEvent.recurrence != null) { //TODO: Check, if we can receive instances without pushing this event into calendar - calendarEvents.instances(defaultCalendar, incomingEvent.id).setMaxResults(50).execute().items.forEach { event -> + calendarEvents.instances(workspaceCalendar, incomingEvent.id).setMaxResults(50).execute().items.forEach { event -> if (!checkSingleEventCollision(event, workspaceCalendar)) { return@checkBookingAvailable false } else { @@ -417,11 +440,9 @@ class BookingCalendarRepository( * Contains collision condition. Checks collision between single event from param and all saved events from event.start (leftBorder) until event.end (rightBorder) * * @param event: [Event] - Must take only SAVED event - * @author Kiselev Danil * */ private fun checkSingleEventCollision(event: Event, workspaceCalendar: String): Boolean { - val savedEvents = basicQuery(event.start.dateTime.value, event.end.dateTime.value) - .setQ(workspaceCalendar) + val savedEvents = basicQuery(event.start.dateTime.value, event.end.dateTime.value, true, workspaceCalendar) .execute().items for (i in savedEvents) { if ( diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt index e55df195b..d67893ecf 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt @@ -140,7 +140,7 @@ class BookingService( workspaceId != null -> { val workspace = workspaceRepository.findById(workspaceId) ?: throw InstanceNotFoundException( - UserEntity::class, "User with id $workspaceId not found", workspaceId + UserEntity::class, "Workspace with id $workspaceId not found", workspaceId ) if (workspace.tag == "meeting") { diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/calendar/repository/CalendarIdsRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/calendar/repository/CalendarIdsRepository.kt index a721a76bd..dd422e37f 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/calendar/repository/CalendarIdsRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/calendar/repository/CalendarIdsRepository.kt @@ -25,8 +25,6 @@ class CalendarIdsRepository( /** * @return String - id of calendar for specified workspace - * - * @author Danil Kiselev * */ fun findByWorkspace(workspaceId: UUID): String { logger.debug("[findByWorkspace] retrieving a calendar id for workspace with id={}", workspaceId.toString()) @@ -38,7 +36,6 @@ class CalendarIdsRepository( /** * @param calendarId * @return [Workspace] model by calendar id (String) - * @author Danil Kiselev * */ fun findWorkspaceById(calendarId: String): Workspace { logger.debug("[findWorkspaceById] retrieving a workspace with calendar id={}", calendarId) @@ -59,16 +56,8 @@ class CalendarIdsRepository( /** * Finds all Google calendar ids * @return all calendar ids from database including default - * @author Danil Kiselev * */ fun findAllCalendarsId(): List { - val list = mutableListOf() - list.add( - config.propertyOrNull("auth.app.defaultAppEmail")?.getString() ?: throw Exception( - "Config file does not contain default gmail value" - ) - ) - db.calendarIds.toList().forEach { list.add(it.calendarId) } - return list + return db.calendarIds.toList().map { it.calendarId } } } \ No newline at end of file diff --git a/effectiveOfficeBackend/src/main/resources/application.conf b/effectiveOfficeBackend/src/main/resources/application.conf index 9eec39aa4..2a6ba004c 100644 --- a/effectiveOfficeBackend/src/main/resources/application.conf +++ b/effectiveOfficeBackend/src/main/resources/application.conf @@ -1,6 +1,6 @@ ktor { deployment { - port = 8080 + port = 8081 host = "0.0.0.0" } } From 15b6be00240fe6004481e442a7b0cddd6f0a32fc Mon Sep 17 00:00:00 2001 From: Daniil Zavyalov Date: Mon, 4 Mar 2024 00:18:46 +0600 Subject: [PATCH 2/5] [~] fix --- effectiveOfficeBackend/src/main/resources/application.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effectiveOfficeBackend/src/main/resources/application.conf b/effectiveOfficeBackend/src/main/resources/application.conf index 2a6ba004c..9eec39aa4 100644 --- a/effectiveOfficeBackend/src/main/resources/application.conf +++ b/effectiveOfficeBackend/src/main/resources/application.conf @@ -1,6 +1,6 @@ ktor { deployment { - port = 8081 + port = 8080 host = "0.0.0.0" } } From d9d759d0796d45dbcfff5d9d5310d0ccb9a9d43e Mon Sep 17 00:00:00 2001 From: Daniil Zavyalov Date: Wed, 6 Mar 2024 20:26:11 +0600 Subject: [PATCH 3/5] [~] fix participants converting --- .../features/booking/converters/GoogleCalendarConverter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt index 0c87bd629..06f9c1ef5 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt @@ -62,7 +62,7 @@ class GoogleCalendarConverter( val recurrence = event.recurrence?.toString()?.getRecurrence()?.toDto() val dto = BookingDTO( owner = getUser(email), - participants = event.attendees?.filter { !(it?.resource ?: true) }?.map { getUser(it.email) } ?: listOf(), + participants = event.attendees?.filter { !(it?.resource ?: false) }?.map { getUser(it.email) } ?: listOf(), workspace = getWorkspace( event.attendees?.firstOrNull { it?.resource ?: false }?.email ?: run { From 696096f5f344a4543ab57c030756876a822daacc Mon Sep 17 00:00:00 2001 From: Daniil Zavyalov Date: Thu, 7 Mar 2024 13:43:03 +0600 Subject: [PATCH 4/5] [~] refactor code --- .../repository/BookingCalendarRepository.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt index c46efcd1e..f3cc643b6 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt @@ -148,7 +148,8 @@ class BookingCalendarRepository( eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity" ) val workspaceCalendarId = getCalendarIdByWorkspace(workspaceId) - val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo, true, workspaceCalendarId) + val getSingleEvents = true + val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo, getSingleEvents, workspaceCalendarId) .execute().items return eventsWithWorkspace.toList().map { googleCalendarConverter.toBookingModel(it) } @@ -185,20 +186,6 @@ class BookingCalendarRepository( ) } - private fun getAllEvents2( - calendarIds: List, - eventRangeFrom: Long, - eventRangeTo: Long? - ): List = runBlocking { - val res = calendarIds.map { calendarId -> - async { - basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) - .execute().items - } - } - res.awaitAll().flatten() - } - private fun getEventsWithQParam( calendarIds: List, q: String, @@ -208,8 +195,12 @@ class BookingCalendarRepository( val executor = Executors.newFixedThreadPool(calendarIds.size) val futures = calendarIds.map { calendarId -> CompletableFuture.supplyAsync { - basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) - .setQ(q) + basicQuery( + timeMin = eventRangeFrom, + timeMax = eventRangeTo, + singleEvents = true, + calendarId = calendarId + ).setQ(q) .execute().items } } @@ -231,8 +222,12 @@ class BookingCalendarRepository( val executor = Executors.newFixedThreadPool(calendarIds.size) val futures = calendarIds.map { calendarId -> CompletableFuture.supplyAsync { - basicQuery(eventRangeFrom, eventRangeTo, true, calendarId) - .execute().items + basicQuery( + timeMin = eventRangeFrom, + timeMax = eventRangeTo, + singleEvents = true, + calendarId = calendarId + ).execute().items } } val allEvents = CompletableFuture.allOf(*futures.toTypedArray()) From e7abdc020ea0eb486427f8aec9485b5f426e237e Mon Sep 17 00:00:00 2001 From: Daniil Zavyalov Date: Thu, 7 Mar 2024 17:02:02 +0600 Subject: [PATCH 5/5] [+] add notification logging --- .../notifications/routes/CalendarNotificationsRouting.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/notifications/routes/CalendarNotificationsRouting.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/notifications/routes/CalendarNotificationsRouting.kt index 9317ec37b..3480ee50e 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/notifications/routes/CalendarNotificationsRouting.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/notifications/routes/CalendarNotificationsRouting.kt @@ -3,10 +3,13 @@ package office.effective.features.notifications.routes import io.github.smiley4.ktorswaggerui.dsl.route import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +import office.effective.common.notifications.FcmNotificationSender import office.effective.common.notifications.INotificationSender import org.koin.core.context.GlobalContext +import org.slf4j.LoggerFactory /** * Route for Google calendar push notifications @@ -16,6 +19,8 @@ fun Route.calendarNotificationsRouting() { val messageSender: INotificationSender = GlobalContext.get().get() post() { + val logger = LoggerFactory.getLogger(FcmNotificationSender::class.java) + logger.info("[calendarNotificationsRouting] received push notification: \n{}", call.receive()) messageSender.sendEmptyMessage("booking") call.respond(HttpStatusCode.OK) }