Skip to content
Open
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 @@ -35,6 +35,7 @@ import com.paligot.confily.backend.qanda.infrastructure.exposed.QAndAActionEntit
import com.paligot.confily.backend.qanda.infrastructure.exposed.QAndAActionsTable
import com.paligot.confily.backend.qanda.infrastructure.exposed.QAndAEntity
import com.paligot.confily.backend.qanda.infrastructure.exposed.toModel
import com.paligot.confily.backend.quiz.infrastructure.exposed.QuizQuestionEntity
import com.paligot.confily.backend.speakers.infrastructure.exposed.SpeakerEntity
import com.paligot.confily.backend.team.infrastructure.exposed.TeamEntity
import com.paligot.confily.backend.team.infrastructure.exposed.TeamGroupEntity
Expand Down Expand Up @@ -170,6 +171,8 @@ class EventRepositoryExposed(
hasMenus = menus.isNotEmpty(),
hasQAndA = qanda.isNotEmpty(),
hasBilletWebTicket = integration != null,
hasQuiz = features1[FeatureKey.Quiz] == true ||
QuizQuestionEntity.findByEvent(eventUuid).empty().not(),
openFeedbackEnabled = features1[FeatureKey.OpenFeedback] ?: true
)
val socials = EventSocialsTable.findByEventId(eventUuid)
Expand Down Expand Up @@ -236,6 +239,8 @@ class EventRepositoryExposed(
hasMenus = menus.isNotEmpty(),
hasQAndA = qanda.isNotEmpty(),
hasBilletWebTicket = integration != null,
hasQuiz = features1[FeatureKey.Quiz] == true ||
QuizQuestionEntity.findByEvent(eventUuid).empty().not(),
openFeedbackEnabled = features1[FeatureKey.OpenFeedback] ?: true
)
val socials = EventSocialsTable.findByEventId(eventUuid)
Expand Down Expand Up @@ -293,6 +298,8 @@ class EventRepositoryExposed(
hasMenus = menus.isNotEmpty(),
hasQAndA = qanda.empty().not(),
hasBilletWebTicket = integration != null,
hasQuiz = features1[FeatureKey.Quiz] == true ||
QuizQuestionEntity.findByEvent(eventUuid).empty().not(),
openFeedbackEnabled = features1[FeatureKey.OpenFeedback] ?: true
)
val socials = EventSocialsTable.findByEventId(eventUuid)
Expand Down Expand Up @@ -352,6 +359,8 @@ class EventRepositoryExposed(
hasMenus = menus.isNotEmpty(),
hasQAndA = qanda.empty().not(),
hasBilletWebTicket = integration != null,
hasQuiz = features1[FeatureKey.Quiz] == true ||
QuizQuestionEntity.findByEvent(eventUuid).empty().not(),
openFeedbackEnabled = features1[FeatureKey.OpenFeedback] ?: true
)

Expand Down Expand Up @@ -445,6 +454,8 @@ class EventRepositoryExposed(
hasMenus = menus.isNotEmpty(),
hasQAndA = true,
hasBilletWebTicket = true,
hasQuiz = features1[FeatureKey.Quiz] == true ||
QuizQuestionEntity.findByEvent(eventUuid).empty().not(),
openFeedbackEnabled = features1[FeatureKey.OpenFeedback] ?: true
),
team = groups.associate { group ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ object EventFeaturesTable : UUIDTable("event_features") {

enum class FeatureKey(val key: String) {
Networking("has_networking"),
OpenFeedback("open_feedback_enabled")
OpenFeedback("open_feedback_enabled"),
Quiz("has_quiz")
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import java.util.UUID

class QuizQuestionEntity(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<QuizQuestionEntity>(QuizQuestionsTable) {
fun findByEvent(eventId: UUID): SizedIterable<QuizQuestionEntity> = this
.find { QuizQuestionsTable.eventId eq eventId }

fun findByPartner(partnerId: UUID): SizedIterable<QuizQuestionEntity> = this
.find { QuizQuestionsTable.partnerId eq partnerId }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.paligot.confily.backend.events

import com.paligot.confily.backend.addresses.infrastructure.provider.GeocodeApi
import com.paligot.confily.backend.events.application.EventRepositoryExposed
import com.paligot.confily.backend.events.infrastructure.exposed.EventEntity
import com.paligot.confily.backend.events.infrastructure.exposed.EventFeatureEntity
import com.paligot.confily.backend.events.infrastructure.exposed.FeatureKey
import com.paligot.confily.backend.internals.infrastructure.exposed.DatabaseFactory
import com.paligot.confily.backend.quiz.QuizTestFixtures
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class EventFeaturesQuizTest {
private lateinit var database: Database
private lateinit var repository: EventRepositoryExposed

@BeforeTest
fun setup() {
database = DatabaseFactory.createTestDatabase()
repository = EventRepositoryExposed(database, mockk<GeocodeApi>(relaxed = true))
}

@Test
fun `hasQuiz is false for an event with no quiz content`() = runBlocking {
val eventId = QuizTestFixtures.createEvent(database)
assertFalse(repository.getV4(eventId.toString()).features.hasQuiz)
}

@Test
fun `hasQuiz is true when the event has at least one quiz question`() = runBlocking {
val eventId = QuizTestFixtures.createEvent(database)
val partnerId = QuizTestFixtures.createPartner(database, eventId, quizCode = "ABCD")
QuizTestFixtures.createQuestion(database, eventId, partnerId, "Q1", listOf("a", "b"), correctIndex = 0)
assertTrue(repository.getV4(eventId.toString()).features.hasQuiz)
}

@Test
fun `hasQuiz is true when the Quiz feature flag is enabled even without questions`() = runBlocking {
val eventId = QuizTestFixtures.createEvent(database)
transaction(database) {
EventFeatureEntity.new {
this.event = EventEntity[eventId]
this.featureKey = FeatureKey.Quiz
this.enabled = true
}
}
assertTrue(repository.getV4(eventId.toString()).features.hasQuiz)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.paligot.confily.backend.quiz

import com.paligot.confily.backend.internals.infrastructure.exposed.DatabaseFactory
import com.paligot.confily.backend.quiz.infrastructure.exposed.QuizQuestionEntity
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class QuizQuestionEntityTest {
private lateinit var database: Database

@BeforeTest
fun setup() {
database = DatabaseFactory.createTestDatabase()
}

@Test
fun `findByEvent returns questions for the event and is empty for another`() {
val eventId = QuizTestFixtures.createEvent(database)
val otherEventId = QuizTestFixtures.createEvent(database)
val partnerId = QuizTestFixtures.createPartner(database, eventId, quizCode = "ABCD")
QuizTestFixtures.createQuestion(database, eventId, partnerId, "Q1", listOf("a", "b"), correctIndex = 0)

transaction(database) {
assertTrue(QuizQuestionEntity.findByEvent(eventId).empty().not())
assertEquals(1, QuizQuestionEntity.findByEvent(eventId).count())
assertTrue(QuizQuestionEntity.findByEvent(otherEventId).empty())
}
}
}
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version
ktor-client-ios = { group = "io.ktor", name = "ktor-client-ios", version.ref = "ktor" }
ktor-client-java = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" }
ktor-client-js = { group = "io.ktor", name = "ktor-client-js", version.ref = "ktor" }
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }

ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import com.paligot.confily.models.CreatedMap
import com.paligot.confily.models.EventList
import com.paligot.confily.models.EventMap
import com.paligot.confily.models.EventV5
import com.paligot.confily.models.LeaderboardEntry
import com.paligot.confily.models.PartnersActivities
import com.paligot.confily.models.QuizQuestion
import com.paligot.confily.models.QuizSubmissionResult
import com.paligot.confily.models.inputs.MapInput
import com.paligot.confily.models.inputs.QuizPlayerInput
import com.paligot.confily.models.inputs.QuizSubmissionInput
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.HttpClientEngine
Expand All @@ -25,6 +30,7 @@ import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -115,6 +121,39 @@ class ConferenceApi(
)
}.body()

suspend fun registerQuizPlayer(eventId: String, input: QuizPlayerInput) {
client.post("$baseUrl/events/$eventId/quiz/players") {
contentType(ContentType.Application.Json)
setBody(input)
}
}

suspend fun fetchQuizQuestions(eventId: String, code: String): List<QuizQuestion> {
val response = client.get("$baseUrl/events/$eventId/quiz/partners/$code")
if (response.status == HttpStatusCode.NotFound) throw QuizException.CodeNotFound(code)
return response.body()
}

suspend fun submitQuiz(
eventId: String,
code: String,
input: QuizSubmissionInput
): QuizSubmissionResult {
val response = client.post("$baseUrl/events/$eventId/quiz/partners/$code/submit") {
contentType(ContentType.Application.Json)
setBody(input)
}
when (response.status) {
HttpStatusCode.Conflict -> throw QuizException.AlreadySubmitted()
HttpStatusCode.NotFound -> throw QuizException.PlayerNotRegistered()
else -> Unit
}
return response.body()
}

suspend fun fetchQuizLeaderboard(eventId: String): List<LeaderboardEntry> =
client.get("$baseUrl/events/$eventId/quiz/leaderboard").body()

companion object {
fun create(
baseUrl: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.paligot.confily.core.api

sealed class QuizException(message: String) : Exception(message) {
class CodeNotFound(code: String) : QuizException("No quiz found for code $code")
class AlreadySubmitted : QuizException("Answers already submitted for this quiz")
class PlayerNotRegistered : QuizException("Player not registered for this device")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE FeaturesActivated ADD COLUMN has_quiz INTEGER NOT NULL DEFAULT 0;
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ has_qanda INTEGER AS Boolean NOT NULL DEFAULT 0,
has_billet_web_ticket INTEGER AS Boolean NOT NULL DEFAULT 0,
has_team_members INTEGER AS Boolean NOT NULL DEFAULT 0,
has_maps INTEGER AS Boolean NOT NULL DEFAULT 0,
has_quiz INTEGER AS Boolean NOT NULL DEFAULT 0,
open_feedback_enabled INTEGER AS Boolean NOT NULL DEFAULT 1
);

insertFeatures:
INSERT OR REPLACE INTO FeaturesActivated(
event_id, has_networking, has_speaker_list, has_partner_list, has_menus, has_qanda, has_billet_web_ticket, has_team_members, has_maps, open_feedback_enabled
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
event_id, has_networking, has_speaker_list, has_partner_list, has_menus, has_qanda, has_billet_web_ticket, has_team_members, has_maps, has_quiz, open_feedback_enabled
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);

selectFeatures:
SELECT event_id, has_networking, has_speaker_list, has_partner_list, has_menus, has_qanda, has_billet_web_ticket, has_team_members, has_maps, open_feedback_enabled
SELECT event_id, has_networking, has_speaker_list, has_partner_list, has_menus, has_qanda, has_billet_web_ticket, has_team_members, has_maps, has_quiz, open_feedback_enabled
FROM FeaturesActivated
WHERE event_id == ?;
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.paligot.confily.core.di

import android.content.Context
import com.paligot.confily.core.DeviceIdProvider
import com.paligot.confily.core.DeviceIdProviderAndroid
import com.paligot.confily.core.Platform
import com.paligot.confily.core.QrCodeGenerator
import com.paligot.confily.core.QrCodeGeneratorAndroid
Expand Down Expand Up @@ -33,4 +35,5 @@ actual val platformModule = module {
}
single<String>(named(AcceptLanguageNamed)) { Locale.getDefault().toLanguageTag() }
single<QrCodeGenerator> { QrCodeGeneratorAndroid() }
single<DeviceIdProvider> { DeviceIdProviderAndroid(androidContext()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.paligot.confily.core.events.EventRepository
import com.paligot.confily.core.maps.MapRepository
import com.paligot.confily.core.networking.UserRepository
import com.paligot.confily.core.partners.PartnerRepository
import com.paligot.confily.core.quiz.QuizRepository
import com.paligot.confily.core.schedules.SessionRepository
import com.paligot.confily.core.speakers.SpeakerRepository
import org.koin.dsl.module
Expand Down Expand Up @@ -56,4 +57,11 @@ val repositoriesModule = module {
mapDao = get()
)
}
single {
QuizRepository.Factory.create(
api = get(),
settings = get(),
deviceIdProvider = get()
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.paligot.confily.core.di

import com.paligot.confily.core.AlarmScheduler
import com.paligot.confily.core.DeviceIdProvider
import com.paligot.confily.core.DeviceIdProviderDesktop
import com.paligot.confily.core.Platform
import com.paligot.confily.core.QrCodeGenerator
import com.paligot.confily.core.QrCodeGeneratorJvm
Expand All @@ -22,4 +24,5 @@ actual val platformModule = module {
single<String>(named(AcceptLanguageNamed)) { Locale.getDefault().toLanguageTag() }
single<QrCodeGenerator> { QrCodeGeneratorJvm() }
single<AlarmScheduler> { AlarmScheduler(repository = get()) }
single<DeviceIdProvider> { DeviceIdProviderDesktop() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.paligot.confily.core.networking.UserInteractor
import com.paligot.confily.core.networking.UserRepository
import com.paligot.confily.core.partners.PartnerInteractor
import com.paligot.confily.core.partners.PartnerRepository
import com.paligot.confily.core.quiz.QuizRepository
import com.paligot.confily.core.schedules.SessionRepository
import com.paligot.confily.core.sessions.SessionInteractor
import com.paligot.confily.core.speakers.SpeakerInteractor
Expand Down Expand Up @@ -35,6 +36,7 @@ class RepositoryHelper : KoinComponent {
val eventRepository: EventRepository by inject()
val speakerRepository: SpeakerRepository by inject()
val userRepository: UserRepository by inject()
val quizRepository: QuizRepository by inject()
}

class InteractorHelper : KoinComponent {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.paligot.confily.core.di

import com.paligot.confily.core.DeviceIdProvider
import com.paligot.confily.core.DeviceIdProviderIos
import com.paligot.confily.core.Platform
import com.paligot.confily.core.QrCodeGenerator
import com.paligot.confily.core.QrCodeGeneratoriOS
Expand All @@ -23,4 +25,5 @@ actual val platformModule = module {
single<ObservableSettings> { NSUserDefaultsSettings(standardUserDefaults) }
single<String>(named(AcceptLanguageNamed)) { NSLocale.preferredLanguages.first().toString() }
single<QrCodeGenerator> { QrCodeGeneratoriOS() }
single<DeviceIdProvider> { DeviceIdProviderIos() }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.paligot.confily.core.di

import com.paligot.confily.core.DeviceIdProvider
import com.paligot.confily.core.DeviceIdProviderWasmJs
import com.paligot.confily.core.Platform
import com.paligot.confily.core.QrCodeGenerator
import com.paligot.confily.core.QrCodeGeneratorWasm
Expand All @@ -21,4 +23,5 @@ actual val platformModule: Module = module {
navigatorLanguage().unsafeCast<JsString>().toString()
}
single<QrCodeGenerator> { QrCodeGeneratorWasm() }
single<DeviceIdProvider> { DeviceIdProviderWasmJs() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,18 @@ class ConferenceSettings(

fun upsertOnlyFavoritesFlag(onlyFavorites: Boolean) =
settings.putBoolean("ONLY_FAVORITES", onlyFavorites)

fun getQuizDeviceId(): String? = settings.getStringOrNull("QUIZ_DEVICE_ID")

fun putQuizDeviceId(deviceId: String) = settings.putString("QUIZ_DEVICE_ID", deviceId)

fun fetchQuizUsername(): Flow<String?> = settings.getStringOrNullFlow("QUIZ_USERNAME")

fun getQuizUsername(): String? = settings.getStringOrNull("QUIZ_USERNAME")

fun putQuizUsername(username: String) = settings.putString("QUIZ_USERNAME", username)

fun getQuizResults(): String? = settings.getStringOrNull("QUIZ_RESULTS")

fun putQuizResults(resultsJson: String) = settings.putString("QUIZ_RESULTS", resultsJson)
}
Loading
Loading