Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend #242

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading