From b35a13aba4a9b5571405b893890f4a7bff30be74 Mon Sep 17 00:00:00 2001 From: Gerard Paligot Date: Tue, 7 May 2024 23:50:40 +0200 Subject: [PATCH] feat(backend): create service for /planning --- .../devfest/backend/events/EventRepository.kt | 77 +++++++++++++++++++ .../devfest/backend/events/EventRouting.kt | 19 +++++ .../backend/schedulers/ScheduleMappers.kt | 23 +++++- .../backend/sessions/SessionMappers.kt | 7 ++ .../gdglille/devfest/models/PlanningItem.kt | 39 ++++++++++ .../gdglille/devfest/models/ScheduleItem.kt | 2 +- 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/PlanningItem.kt diff --git a/backend/src/main/java/org/gdglille/devfest/backend/events/EventRepository.kt b/backend/src/main/java/org/gdglille/devfest/backend/events/EventRepository.kt index 8e907059..6caf4813 100644 --- a/backend/src/main/java/org/gdglille/devfest/backend/events/EventRepository.kt +++ b/backend/src/main/java/org/gdglille/devfest/backend/events/EventRepository.kt @@ -1,23 +1,34 @@ package org.gdglille.devfest.backend.events +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import org.gdglille.devfest.backend.NotAcceptableException import org.gdglille.devfest.backend.NotFoundException import org.gdglille.devfest.backend.categories.CategoryDao +import org.gdglille.devfest.backend.categories.CategoryDb import org.gdglille.devfest.backend.formats.FormatDao +import org.gdglille.devfest.backend.formats.FormatDb import org.gdglille.devfest.backend.internals.date.FormatterPattern import org.gdglille.devfest.backend.internals.date.format import org.gdglille.devfest.backend.partners.PartnerDao import org.gdglille.devfest.backend.partners.Sponsorship import org.gdglille.devfest.backend.partners.convertToModel import org.gdglille.devfest.backend.qanda.QAndADao +import org.gdglille.devfest.backend.schedulers.ScheduleDb import org.gdglille.devfest.backend.schedulers.ScheduleItemDao import org.gdglille.devfest.backend.schedulers.convertToModel +import org.gdglille.devfest.backend.schedulers.convertToPlanningEventModel +import org.gdglille.devfest.backend.schedulers.convertToPlanningTalkModel +import org.gdglille.devfest.backend.sessions.EventSessionDb import org.gdglille.devfest.backend.sessions.SessionDao +import org.gdglille.devfest.backend.sessions.SessionDb +import org.gdglille.devfest.backend.sessions.TalkDb import org.gdglille.devfest.backend.sessions.convertToModel +import org.gdglille.devfest.backend.sessions.convertToModelInfo import org.gdglille.devfest.backend.speakers.SpeakerDao +import org.gdglille.devfest.backend.speakers.SpeakerDb import org.gdglille.devfest.backend.third.parties.geocode.GeocodeApi import org.gdglille.devfest.backend.third.parties.geocode.convertToDb import org.gdglille.devfest.models.Agenda @@ -25,6 +36,7 @@ import org.gdglille.devfest.models.CreatedEvent import org.gdglille.devfest.models.Event import org.gdglille.devfest.models.EventList import org.gdglille.devfest.models.EventPartners +import org.gdglille.devfest.models.PlanningItem import org.gdglille.devfest.models.inputs.CoCInput import org.gdglille.devfest.models.inputs.CreatingEventInput import org.gdglille.devfest.models.inputs.EventInput @@ -146,4 +158,69 @@ class EventRepository( }.awaitAll().associate { it }.toSortedMap() return@coroutineScope Agenda(talks = schedules) } + + suspend fun planning(eventDb: EventDb): Map>> = coroutineScope { + val sessions = sessionDao.getAll(eventDb.slugId) + val speakers = speakerDao.getAll(eventDb.slugId) + val categories = categoryDao.getAll(eventDb.slugId) + val formats = formatDao.getAll(eventDb.slugId) + return@coroutineScope scheduleItemDao.getAll(eventDb.slugId) + .groupBy { LocalDateTime.parse(it.startTime).format(FormatterPattern.YearMonthDay) } + .entries.map { schedulesByDay -> + schedulesByDay(schedulesByDay, sessions, speakers, categories, formats, eventDb) + } + .awaitAll() + .sortedBy { it.first } + .associate { it } + .toMap() + } + + @Suppress("LongParameterList") + private fun CoroutineScope.schedulesByDay( + schedulesByDay: Map.Entry>, + sessions: List, + speakers: List, + categories: List, + formats: List, + eventDb: EventDb + ) = async { + return@async schedulesByDay.key to schedulesByDay.value + .groupBy { LocalDateTime.parse(it.startTime).format(FormatterPattern.HoursMinutes) } + .entries.map { schedulesBySlot -> + schedulesBySlot.key to schedulesBySlot.value + .map { schedule -> + schedulesBySlot(schedule, sessions, speakers, categories, formats, eventDb) + } + .awaitAll() + .sortedBy { it.order } + } + .sortedBy { it.first } + .associate { it } + .toMap() + } + + @Suppress("LongParameterList") + private fun CoroutineScope.schedulesBySlot( + schedule: ScheduleDb, + sessions: List, + speakers: List, + categories: List, + formats: List, + eventDb: EventDb + ) = async { + val session = sessions.find { it.id == schedule.talkId } + ?: throw NotFoundException("Session ${schedule.id} not found") + when (session) { + is EventSessionDb -> schedule.convertToPlanningEventModel(session.convertToModelInfo()) + + is TalkDb -> { + val speakersTalk = speakers.filter { session.speakerIds.contains(it.id) } + val category = categories.find { it.id == session.category } + val format = formats.find { it.id == session.format } + schedule.convertToPlanningTalkModel( + session.convertToModel(speakersTalk, category, format, eventDb) + ) + } + } + } } diff --git a/backend/src/main/java/org/gdglille/devfest/backend/events/EventRouting.kt b/backend/src/main/java/org/gdglille/devfest/backend/events/EventRouting.kt index 3917e019..85913672 100644 --- a/backend/src/main/java/org/gdglille/devfest/backend/events/EventRouting.kt +++ b/backend/src/main/java/org/gdglille/devfest/backend/events/EventRouting.kt @@ -130,6 +130,25 @@ fun Route.registerEventRoutes( val input = call.receiveValidated() call.respond(HttpStatusCode.OK, repository.updateFeatures(eventId, apiKey, input)) } + get("/events/{eventId}/planning") { + val eventId = call.parameters["eventId"]!! + val event = eventDao.get(eventId) ?: throw NotFoundException("Event $eventId Not Found") + val zoneId = ZoneId.of("Europe/Paris") + val lastModified = ZonedDateTime.of( + LocalDateTime.ofInstant(Instant.ofEpochMilli(event.agendaUpdatedAt), zoneId), + zoneId + ) + val etag = lastModified.hashCode().toString() + val version = EntityTagVersion(etag) + val result = version.check(call.request.headers) + if (result == VersionCheckResult.NOT_MODIFIED) { + call.respond(HttpStatusCode.NotModified) + } else { + call.response.lastModified(lastModified) + call.response.etag(etag) + call.respond(HttpStatusCode.OK, repository.planning(event)) + } + } get("/events/{eventId}/agenda") { val eventId = call.parameters["eventId"]!! val event = eventDao.get(eventId) ?: throw NotFoundException("Event $eventId Not Found") diff --git a/backend/src/main/java/org/gdglille/devfest/backend/schedulers/ScheduleMappers.kt b/backend/src/main/java/org/gdglille/devfest/backend/schedulers/ScheduleMappers.kt index 5693cf3f..ad349461 100644 --- a/backend/src/main/java/org/gdglille/devfest/backend/schedulers/ScheduleMappers.kt +++ b/backend/src/main/java/org/gdglille/devfest/backend/schedulers/ScheduleMappers.kt @@ -1,7 +1,10 @@ package org.gdglille.devfest.backend.schedulers +import org.gdglille.devfest.backend.NotAcceptableException import org.gdglille.devfest.backend.internals.date.FormatterPattern import org.gdglille.devfest.backend.internals.date.format +import org.gdglille.devfest.models.Info +import org.gdglille.devfest.models.PlanningItem import org.gdglille.devfest.models.ScheduleItem import org.gdglille.devfest.models.ScheduleItemV3 import org.gdglille.devfest.models.ScheduleItemV4 @@ -9,6 +12,24 @@ import org.gdglille.devfest.models.Talk import org.gdglille.devfest.models.inputs.ScheduleInput import java.time.LocalDateTime +fun ScheduleDb.convertToPlanningTalkModel(talk: Talk) = PlanningItem.TalkItem( + id = this.id, + order = order ?: 0, + startTime = this.startTime, + endTime = this.endTime, + room = this.room, + talk = talk +) + +fun ScheduleDb.convertToPlanningEventModel(info: Info) = PlanningItem.EventItem( + id = this.id, + order = order ?: 0, + startTime = this.startTime, + endTime = this.endTime, + room = this.room, + info = info +) + fun ScheduleDb.convertToModel(talk: Talk?) = ScheduleItem( id = this.id, order = order ?: 0, @@ -36,7 +57,7 @@ fun ScheduleDb.convertToModelV4() = ScheduleItemV4( startTime = this.startTime, endTime = this.endTime, room = this.room, - sessionId = talkId + sessionId = talkId ?: throw NotAcceptableException("Talk id can't be null with version 4") ) fun ScheduleInput.convertToDb(endTime: String, talkId: String? = null) = ScheduleDb( diff --git a/backend/src/main/java/org/gdglille/devfest/backend/sessions/SessionMappers.kt b/backend/src/main/java/org/gdglille/devfest/backend/sessions/SessionMappers.kt index 64a6b901..ddf70e5f 100644 --- a/backend/src/main/java/org/gdglille/devfest/backend/sessions/SessionMappers.kt +++ b/backend/src/main/java/org/gdglille/devfest/backend/sessions/SessionMappers.kt @@ -7,6 +7,7 @@ import org.gdglille.devfest.backend.events.openFeedbackUrl import org.gdglille.devfest.backend.formats.FormatDb import org.gdglille.devfest.backend.speakers.SpeakerDb import org.gdglille.devfest.backend.speakers.convertToModel +import org.gdglille.devfest.models.Info import org.gdglille.devfest.models.Session import org.gdglille.devfest.models.Talk import org.gdglille.devfest.models.TalkV3 @@ -39,6 +40,12 @@ fun EventSessionDb.convertToModelEventSession(): Session = Session.Event( description = description ) +fun EventSessionDb.convertToModelInfo(): Info = Info( + id = id, + title = title, + description = description +) + fun TalkDb.convertToModel( speakers: List, category: CategoryDb?, diff --git a/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/PlanningItem.kt b/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/PlanningItem.kt new file mode 100644 index 00000000..9eb57c83 --- /dev/null +++ b/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/PlanningItem.kt @@ -0,0 +1,39 @@ +package org.gdglille.devfest.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +sealed class PlanningItem { + abstract val id: String + abstract val order: Int + + @Serializable + @SerialName("talk-session") + data class TalkItem( + override val id: String, + override val order: Int, + val startTime: String, + val endTime: String, + val room: String, + val talk: Talk + ) : PlanningItem() + + @Serializable + @SerialName("event-session") + data class EventItem( + override val id: String, + override val order: Int, + val startTime: String, + val endTime: String, + val room: String, + val info: Info + ) : PlanningItem() +} + +@Serializable +data class Info( + val id: String, + val title: String, + val description: String? +) diff --git a/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/ScheduleItem.kt b/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/ScheduleItem.kt index f28f79da..3b28ea8b 100644 --- a/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/ScheduleItem.kt +++ b/shared/models/src/commonMain/kotlin/org/gdglille/devfest/models/ScheduleItem.kt @@ -40,5 +40,5 @@ data class ScheduleItemV4( val endTime: String, val room: String, @SerialName("session_id") - val sessionId: String? + val sessionId: String )