Skip to content

Commit

Permalink
feat(backend): save speaker's avatar on Storage before saving in db.
Browse files Browse the repository at this point in the history
  • Loading branch information
GerardPaligot committed Apr 29, 2024
1 parent 4831187 commit f1c30da
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 16 deletions.
6 changes: 5 additions & 1 deletion backend/src/main/java/org/gdglille/devfest/backend/Server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.events.registerEventRoutes
import org.gdglille.devfest.backend.formats.FormatDao
import org.gdglille.devfest.backend.formats.registerFormatsRoutes
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.internals.helpers.database.BasicDatabase
import org.gdglille.devfest.backend.internals.helpers.database.Database
import org.gdglille.devfest.backend.internals.helpers.drive.GoogleDriveDataSource
Expand Down Expand Up @@ -125,6 +126,7 @@ fun main() {
)
val openPlannerApi = OpenPlannerApi.Factory.create(enableNetworkLogs = true)
val conferenceHallApi = ConferenceHallApi.Factory.create(enableNetworkLogs = true)
val commonApi = CommonApi.Factory.create(enableNetworkLogs = true)
val imageTranscoder = TranscoderImage()
embeddedServer(Netty, PORT) {
install(CORS) {
Expand Down Expand Up @@ -172,7 +174,7 @@ fun main() {
)
route("/events/{eventId}") {
registerQAndAsRoutes(eventDao, qAndADao)
registerSpeakersRoutes(eventDao, speakerDao)
registerSpeakersRoutes(commonApi, eventDao, speakerDao)
registerTalksRoutes(
eventDao,
speakerDao,
Expand Down Expand Up @@ -203,6 +205,7 @@ fun main() {
)
registerConferenceHallRoutes(
conferenceHallApi,
commonApi,
eventDao,
speakerDao,
talkDao,
Expand All @@ -211,6 +214,7 @@ fun main() {
)
registerOpenPlannerRoutes(
openPlannerApi,
commonApi,
eventDao,
speakerDao,
talkDao,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.gdglille.devfest.backend.internals

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.java.Java
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json

class CommonApi(
private val client: HttpClient
) {
suspend fun fetchByteArray(url: String) = client.get(url).body<ByteArray>()

object Factory {
fun create(enableNetworkLogs: Boolean): CommonApi =
CommonApi(
client = HttpClient(Java.create()) {
install(ContentNegotiation) {
json(
Json {
isLenient = true
ignoreUnknownKeys = true
}
)
}
if (enableNetworkLogs) {
install(
Logging
) {
logger = Logger.DEFAULT
level = LogLevel.INFO
}
}
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ fun SpeakerDb.convertToModel(): Speaker = Speaker(
linkedin = this.linkedin
)

fun SpeakerInput.convertToDb(id: String? = null) = SpeakerDb(
fun SpeakerInput.convertToDb(photoUrl: String, id: String? = null) = SpeakerDb(
id = id ?: "",
displayName = this.displayName,
pronouns = this.pronouns,
bio = this.bio,
email = this.email,
jobTitle = this.jobTitle,
company = this.company,
photoUrl = this.photoUrl,
photoUrl = photoUrl,
website = this.website,
twitter = this.twitter,
mastodon = this.mastodon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package org.gdglille.devfest.backend.speakers
import kotlinx.coroutines.coroutineScope
import org.gdglille.devfest.backend.NotFoundException
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.models.inputs.SpeakerInput

class SpeakerRepository(
private val commonApi: CommonApi,
private val eventDao: EventDao,
private val speakerDao: SpeakerDao
) {
Expand All @@ -16,8 +18,11 @@ class SpeakerRepository(
suspend fun create(eventId: String, apiKey: String, speakerInput: SpeakerInput) =
coroutineScope {
val event = eventDao.getVerified(eventId, apiKey)
val speakerDb = speakerInput.convertToDb()
val speakerDb = speakerInput.convertToDb(photoUrl = speakerInput.photoUrl)
val id = speakerDao.createOrUpdate(eventId, speakerDb)
val avatar = commonApi.fetchByteArray(speakerInput.photoUrl)
val bucketItem = speakerDao.saveProfile(eventId = eventId, id = id, content = avatar)
speakerDao.createOrUpdate(eventId, speakerDb.copy(photoUrl = bucketItem.url))
eventDao.updateAgendaUpdatedAt(event)
return@coroutineScope id
}
Expand All @@ -35,7 +40,9 @@ class SpeakerRepository(
speakerInput: SpeakerInput
) = coroutineScope {
val event = eventDao.getVerified(eventId, apiKey)
val speakerDb = speakerInput.convertToDb(speakerId)
val avatar = commonApi.fetchByteArray(speakerInput.photoUrl)
val bucketItem = speakerDao.saveProfile(eventId = eventId, id = speakerId, content = avatar)
val speakerDb = speakerInput.convertToDb(photoUrl = bucketItem.url, speakerId)
eventDao.updateAgendaUpdatedAt(event)
return@coroutineScope speakerDao.createOrUpdate(eventId, speakerDb)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.put
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.receiveValidated
import org.gdglille.devfest.models.inputs.SpeakerInput

fun Route.registerSpeakersRoutes(
commonApi: CommonApi,
eventDao: EventDao,
speakerDao: SpeakerDao
) {
val repository = SpeakerRepository(eventDao, speakerDao)
val repository = SpeakerRepository(commonApi, eventDao, speakerDao)

get("/speakers") {
val eventId = call.parameters["eventId"]!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ class ConferenceHallApi(
private val client: HttpClient,
private val baseUrl: String = "https://${System.getenv("BASE_URL_CONFERENCE_HALL")}/api"
) {
suspend fun fetchSpeakerAvatar(url: String) = client.get(url).body<ByteArray>()

suspend fun fetchEventConfirmed(eventId: String, apiKey: String) =
client.get("$baseUrl/v1/event/$eventId?key=$apiKey&state=confirmed").body<Event>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import org.gdglille.devfest.backend.NotFoundException
import org.gdglille.devfest.backend.categories.CategoryDao
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.formats.FormatDao
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.internals.slug
import org.gdglille.devfest.backend.speakers.SpeakerDao
import org.gdglille.devfest.backend.speakers.convertToDb
import org.gdglille.devfest.backend.talks.TalkDao
import org.gdglille.devfest.models.inputs.third.parties.conferencehall.ImportTalkInput

@Suppress("LongParameterList")
class ConferenceHallRepository(
private val api: ConferenceHallApi,
private val commonApi: CommonApi,
private val eventDao: EventDao,
private val speakerDao: SpeakerDao,
private val talkDao: TalkDao,
Expand Down Expand Up @@ -71,7 +74,7 @@ class ConferenceHallRepository(
.map {
async {
try {
val avatar = api.fetchSpeakerAvatar(it.photoURL!!)
val avatar = commonApi.fetchByteArray(it.photoURL!!)
val bucketItem = speakerDao.saveProfile(eventId, it.uid, avatar)
it.uid to bucketItem.url
} catch (ignored: Throwable) {
Expand All @@ -95,7 +98,7 @@ class ConferenceHallRepository(
val speaker = eventConfirmed.speakers.find { it.uid == speakerId }
?: throw NotFoundException("Speaker $speakerId not found in confirmed talks")
if (speaker.photoURL == null) throw NotAcceptableException("Speaker $speakerId doesn't have a photo")
val avatar = api.fetchSpeakerAvatar(speaker.photoURL)
val avatar = commonApi.fetchByteArray(speaker.photoURL)
val bucketItem = speakerDao.saveProfile(eventId, speaker.uid, avatar)
speakerDao.insert(eventId, speaker.convertToDb(bucketItem.url))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.ktor.server.routing.post
import org.gdglille.devfest.backend.categories.CategoryDao
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.formats.FormatDao
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.receiveValidated
import org.gdglille.devfest.backend.speakers.SpeakerDao
import org.gdglille.devfest.backend.talks.TalkDao
Expand All @@ -16,6 +17,7 @@ import org.gdglille.devfest.models.inputs.third.parties.conferencehall.ImportTal
@Suppress("LongParameterList")
fun Route.registerConferenceHallRoutes(
conferenceHallApi: ConferenceHallApi,
commonApi: CommonApi,
eventDao: EventDao,
speakerDao: SpeakerDao,
talkDao: TalkDao,
Expand All @@ -24,6 +26,7 @@ fun Route.registerConferenceHallRoutes(
) {
val conferenceHallRepo = ConferenceHallRepository(
conferenceHallApi,
commonApi,
eventDao,
speakerDao,
talkDao,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ fun FormatDb.mergeWith(formatOP: FormatOP) = FormatDb(
time = if (this.time != 0) this.time else formatOP.durationMinutes
)

fun SpeakerOP.convertToDb(): SpeakerDb {
fun SpeakerOP.convertToDb(photoUrl: String?): SpeakerDb {
val twitter = socials.find { it.name.lowercase() == "twitter" }?.link
val github = socials.find { it.name.lowercase() == "gitHub" }?.link
val github = socials.find { it.name.lowercase() == "github" }?.link
return SpeakerDb(
id = id,
displayName = name,
Expand Down Expand Up @@ -64,7 +64,7 @@ fun SpeakerOP.convertToDb(): SpeakerDb {
)
}

fun SpeakerDb.mergeWith(speakerOP: SpeakerOP): SpeakerDb {
fun SpeakerDb.mergeWith(photoUrl: String?, speakerOP: SpeakerOP): SpeakerDb {
val twitter = speakerOP.socials.find { it.name.lowercase() == "twitter" }?.link
val github = speakerOP.socials.find { it.name.lowercase() == "gitHub" }?.link
val website = speakerOP.socials.find { it.name.lowercase() == "website" }?.link
Expand All @@ -78,7 +78,7 @@ fun SpeakerDb.mergeWith(speakerOP: SpeakerOP): SpeakerDb {
email = if (this.email == speakerOP.email) this.email else speakerOP.email,
jobTitle = if (this.jobTitle == speakerOP.jobTitle) this.jobTitle else speakerOP.jobTitle,
company = if (this.company == speakerOP.company) this.company else speakerOP.company,
photoUrl = if (this.photoUrl == speakerOP.photoUrl) this.photoUrl else speakerOP.photoUrl ?: "",
photoUrl = if (this.photoUrl == photoUrl) this.photoUrl else photoUrl ?: "",
website = if (this.website == website) this.website else website,
twitter = if (this.twitter == twitter) {
this.twitter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.gdglille.devfest.backend.categories.CategoryDb
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.formats.FormatDao
import org.gdglille.devfest.backend.formats.FormatDb
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.schedulers.ScheduleDb
import org.gdglille.devfest.backend.schedulers.ScheduleItemDao
import org.gdglille.devfest.backend.speakers.SpeakerDao
Expand All @@ -19,6 +20,7 @@ import org.gdglille.devfest.backend.talks.TalkDb
@Suppress("LongParameterList")
class OpenPlannerRepository(
private val openPlannerApi: OpenPlannerApi,
private val commonApi: CommonApi,
private val eventDao: EventDao,
private val speakerDao: SpeakerDao,
private val talkDao: TalkDao,
Expand Down Expand Up @@ -118,16 +120,30 @@ class OpenPlannerRepository(
private suspend fun createOrMergeSpeaker(eventId: String, speaker: SpeakerOP): SpeakerDb {
val existing = speakerDao.get(eventId, speaker.id)
return if (existing == null) {
val item = speaker.convertToDb()
val photoUrl = getAvatarUrl(eventId, speaker)
val item = speaker.convertToDb(photoUrl)
speakerDao.createOrUpdate(eventId, item)
item
} else {
val item = existing.mergeWith(speaker)
val photoUrl = getAvatarUrl(eventId, speaker)
val item = existing.mergeWith(photoUrl, speaker)
speakerDao.createOrUpdate(eventId, item)
item
}
}

private suspend fun getAvatarUrl(eventId: String, speaker: SpeakerOP) = try {
if (speaker.photoUrl != null) {
val avatar = commonApi.fetchByteArray(speaker.photoUrl)
val bucketItem = speakerDao.saveProfile(eventId, speaker.id, avatar)
bucketItem.url
} else {
null
}
} catch (_: Throwable) {
speaker.photoUrl
}

private suspend fun createOrMergeTalks(eventId: String, session: SessionOP): TalkDb {
val existing = talkDao.get(eventId, session.id)
return if (existing == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import org.gdglille.devfest.backend.NotAuthorized
import org.gdglille.devfest.backend.categories.CategoryDao
import org.gdglille.devfest.backend.events.EventDao
import org.gdglille.devfest.backend.formats.FormatDao
import org.gdglille.devfest.backend.internals.CommonApi
import org.gdglille.devfest.backend.schedulers.ScheduleItemDao
import org.gdglille.devfest.backend.speakers.SpeakerDao
import org.gdglille.devfest.backend.talks.TalkDao

@Suppress("LongParameterList")
fun Route.registerOpenPlannerRoutes(
openPlannerApi: OpenPlannerApi,
commonApi: CommonApi,
eventDao: EventDao,
speakerDao: SpeakerDao,
talkDao: TalkDao,
Expand All @@ -25,6 +27,7 @@ fun Route.registerOpenPlannerRoutes(
) {
val repository = OpenPlannerRepository(
openPlannerApi,
commonApi,
eventDao,
speakerDao,
talkDao,
Expand Down
Binary file not shown.

0 comments on commit f1c30da

Please sign in to comment.