Skip to content

Commit

Permalink
Merge pull request #242 from effectivemade/backend
Browse files Browse the repository at this point in the history
Backend
  • Loading branch information
zavyalov-daniil authored Mar 11, 2024
2 parents 22a36fe + e7abdc0 commit 02fc4a8
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -121,7 +118,6 @@ class BookingCalendarRepository(
* @param timeMax
* @param singleEvents
* @param calendarId
* @author Daniil Zavyalov
*/
private fun basicQuery(
timeMin: Long,
Expand All @@ -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
*
Expand All @@ -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<Booking> {
logger.debug(
Expand All @@ -169,19 +148,11 @@ class BookingCalendarRepository(
eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity"
)
val workspaceCalendarId = getCalendarIdByWorkspace(workspaceId)
val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo)
.setQ(workspaceCalendarId)
val getSingleEvents = true
val eventsWithWorkspace = basicQuery(eventRangeFrom, eventRangeTo, getSingleEvents, workspaceCalendarId)
.execute().items

val result: MutableList<Booking> = 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) }
}

/**
Expand Down Expand Up @@ -215,6 +186,60 @@ class BookingCalendarRepository(
)
}

private fun getEventsWithQParam(
calendarIds: List<String>,
q: String,
eventRangeFrom: Long,
eventRangeTo: Long?
): List<Event> {
val executor = Executors.newFixedThreadPool(calendarIds.size)
val futures = calendarIds.map { calendarId ->
CompletableFuture.supplyAsync {
basicQuery(
timeMin = eventRangeFrom,
timeMax = eventRangeTo,
singleEvents = true,
calendarId = 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<String>,
eventRangeFrom: Long,
eventRangeTo: Long?
): List<Event> {
val executor = Executors.newFixedThreadPool(calendarIds.size)
val futures = calendarIds.map { calendarId ->
CompletableFuture.supplyAsync {
basicQuery(
timeMin = eventRangeFrom,
timeMax = eventRangeTo,
singleEvents = true,
calendarId = 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
*
Expand All @@ -234,20 +259,18 @@ class BookingCalendarRepository(
eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity"
)
val userEmail: String = findUserEmailByUserId(ownerId)
val calendars: List<String> = calendarIdsRepository.findAllCalendarsId()

val eventsWithUser = basicQuery(eventRangeFrom, eventRangeTo)
.setQ(userEmail)
.execute().items
val eventsWithUser = getEventsWithQParam(calendars, userEmail, eventRangeFrom, eventRangeTo)

val result = mutableListOf<Booking>()
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
}

Expand All @@ -260,7 +283,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,
Expand All @@ -278,16 +300,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<Booking>()
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)
}
}

Expand All @@ -301,17 +323,16 @@ 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<Booking> {
logger.debug(
"[findAll] retrieving all bookings in range from {} to {}",
Instant.ofEpochMilli(eventRangeFrom),
eventRangeTo?.let { Instant.ofEpochMilli(it) } ?: "infinity"
)
return basicQuery(eventRangeFrom, eventRangeTo).execute().items.map { event ->
googleCalendarConverter.toBookingModel(event)
}
val calendars: List<String> = calendarIdsRepository.findAllCalendarsId()
val events: List<Event> = getAllEvents(calendars, eventRangeFrom, eventRangeTo)
return events.map { googleCalendarConverter.toBookingModel(it) }
}

/**
Expand All @@ -320,7 +341,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)
Expand Down Expand Up @@ -351,7 +371,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)
Expand Down Expand Up @@ -387,7 +406,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(
Expand All @@ -399,7 +417,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 {
Expand All @@ -417,11 +435,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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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)
Expand All @@ -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<String> {
val list = mutableListOf<String>()
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 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String>())
messageSender.sendEmptyMessage("booking")
call.respond(HttpStatusCode.OK)
}
Expand Down

0 comments on commit 02fc4a8

Please sign in to comment.