refactor(): overall refactoring and improvements
This commit is contained in:
@@ -2,6 +2,9 @@ package com.project.movienight.adapters.jellyfin
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinLibraryItemSnapshot
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinRemoteUser
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinSyncSourcePort
|
||||||
import com.project.movienight.config.JellyfinIntegrationProperties
|
import com.project.movienight.config.JellyfinIntegrationProperties
|
||||||
import com.project.movienight.domain.model.ContentType
|
import com.project.movienight.domain.model.ContentType
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -11,39 +14,18 @@ import java.net.http.HttpRequest
|
|||||||
import java.net.http.HttpResponse
|
import java.net.http.HttpResponse
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
data class JellyfinRemoteUser(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class JellyfinLibraryItemSnapshot(
|
|
||||||
val jellyfinItemId: String,
|
|
||||||
val title: String,
|
|
||||||
val description: String,
|
|
||||||
val contentType: ContentType,
|
|
||||||
val releaseYear: Int?,
|
|
||||||
val genres: List<String>,
|
|
||||||
val cast: List<String>,
|
|
||||||
val directors: List<String>,
|
|
||||||
val platformRating: Double?,
|
|
||||||
val imdbRating: Double?,
|
|
||||||
val externalUrl: String?,
|
|
||||||
val jellyfinLibraryId: String?,
|
|
||||||
val isPlayed: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class JellyfinApiClient(
|
class JellyfinApiClient(
|
||||||
private val properties: JellyfinIntegrationProperties,
|
private val properties: JellyfinIntegrationProperties,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
) {
|
) : JellyfinSyncSourcePort {
|
||||||
private val httpClient: HttpClient =
|
private val httpClient: HttpClient =
|
||||||
HttpClient
|
HttpClient
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.connectTimeout(Duration.ofMillis(properties.requestTimeoutMs))
|
.connectTimeout(Duration.ofMillis(properties.requestTimeoutMs))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun fetchUsers(): List<JellyfinRemoteUser> =
|
override fun fetchUsers(): List<JellyfinRemoteUser> =
|
||||||
request("Users")
|
request("Users")
|
||||||
.asItems()
|
.asItems()
|
||||||
.mapNotNull { node ->
|
.mapNotNull { node ->
|
||||||
@@ -51,7 +33,7 @@ class JellyfinApiClient(
|
|||||||
JellyfinRemoteUser(id = id, name = node.fieldText("Name") ?: id)
|
JellyfinRemoteUser(id = id, name = node.fieldText("Name") ?: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchLibraryItems(userId: String): List<JellyfinLibraryItemSnapshot> =
|
override fun fetchLibraryItems(userId: String): List<JellyfinLibraryItemSnapshot> =
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
request(
|
request(
|
||||||
"Users/$userId/Items?Recursive=true&IncludeItemTypes=Movie,Series,Episode&Fields=Genres,People,ProviderIds,Overview,ProductionYear,CommunityRating,OfficialRating,ParentId,UserData",
|
"Users/$userId/Items?Recursive=true&IncludeItemTypes=Movie,Series,Episode&Fields=Genres,People,ProviderIds,Overview,ProductionYear,CommunityRating,OfficialRating,ParentId,UserData",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.project.movienight.adapters.metrics
|
package com.project.movienight.adapters.metrics
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
import com.project.movienight.domain.model.JellyfinSyncSummary
|
import com.project.movienight.domain.model.JellyfinSyncSummary
|
||||||
import com.project.movienight.domain.model.RecommendationEventType
|
import com.project.movienight.domain.model.RecommendationEventType
|
||||||
import io.micrometer.core.instrument.Counter
|
import io.micrometer.core.instrument.Counter
|
||||||
@@ -33,7 +34,7 @@ class BusinessMetricsService(
|
|||||||
|
|
||||||
private val backendWriteFailures: Counter = meterRegistry.counter("business_jellyfin_backend_write_failures_total")
|
private val backendWriteFailures: Counter = meterRegistry.counter("business_jellyfin_backend_write_failures_total")
|
||||||
|
|
||||||
fun recordRecommendationRequest() {
|
override fun recordRecommendationRequest() {
|
||||||
recommendationRequests.increment()
|
recommendationRequests.increment()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +50,11 @@ class BusinessMetricsService(
|
|||||||
ratingsSubmitted.increment()
|
ratingsSubmitted.increment()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recordLibraryEvent() {
|
override fun recordLibraryEvent() {
|
||||||
libraryEvents.increment()
|
libraryEvents.increment()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recordJellyfinSync(summary: JellyfinSyncSummary) {
|
override fun recordJellyfinSync(summary: JellyfinSyncSummary) {
|
||||||
jellyfinSyncRuns.increment()
|
jellyfinSyncRuns.increment()
|
||||||
jellyfinSyncedUsers.increment(summary.syncedUsers.toDouble())
|
jellyfinSyncedUsers.increment(summary.syncedUsers.toDouble())
|
||||||
jellyfinSkippedUsers.increment(summary.skippedUsers.toDouble())
|
jellyfinSkippedUsers.increment(summary.skippedUsers.toDouble())
|
||||||
@@ -61,15 +62,15 @@ class BusinessMetricsService(
|
|||||||
jellyfinSyncDuration.record(summary.durationMs, java.util.concurrent.TimeUnit.MILLISECONDS)
|
jellyfinSyncDuration.record(summary.durationMs, java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recordJellyfinSyncFailure() {
|
override fun recordJellyfinSyncFailure() {
|
||||||
jellyfinSyncFailures.increment()
|
jellyfinSyncFailures.increment()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recordJellyfinUnmappedUser() {
|
override fun recordJellyfinUnmappedUser() {
|
||||||
jellyfinUnmappedUsersGaugeValue.incrementAndGet()
|
jellyfinUnmappedUsersGaugeValue.incrementAndGet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recordBackendWriteFailure() {
|
override fun recordBackendWriteFailure() {
|
||||||
backendWriteFailures.increment()
|
backendWriteFailures.increment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,6 @@
|
|||||||
package com.project.movienight.adapters.persistence.jdbc
|
package com.project.movienight.adapters.persistence.jdbc
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinEventRepositoryPort
|
||||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
|
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
|
||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -7,8 +8,8 @@ import org.springframework.stereotype.Repository
|
|||||||
@Repository
|
@Repository
|
||||||
class JellyfinEventRepository(
|
class JellyfinEventRepository(
|
||||||
private val jdbc: NamedParameterJdbcTemplate,
|
private val jdbc: NamedParameterJdbcTemplate,
|
||||||
) {
|
) : JellyfinEventRepositoryPort {
|
||||||
fun save(
|
override fun save(
|
||||||
eventId: String,
|
eventId: String,
|
||||||
serverId: String?,
|
serverId: String?,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
@@ -37,7 +38,7 @@ class JellyfinEventRepository(
|
|||||||
return jdbc.update(sql, params)
|
return jdbc.update(sql, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(eventId: String) {
|
override fun delete(eventId: String) {
|
||||||
val sql = "DELETE FROM jellyfin_events WHERE event_id = :eventId"
|
val sql = "DELETE FROM jellyfin_events WHERE event_id = :eventId"
|
||||||
val params = MapSqlParameterSource().addValue("eventId", eventId)
|
val params = MapSqlParameterSource().addValue("eventId", eventId)
|
||||||
jdbc.update(sql, params)
|
jdbc.update(sql, params)
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.project.movienight.application.ports.output
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.JellyfinSyncSummary
|
||||||
|
|
||||||
|
interface BusinessMetricsPort {
|
||||||
|
fun recordRecommendationRequest()
|
||||||
|
|
||||||
|
fun recordRatingSubmitted()
|
||||||
|
|
||||||
|
fun recordLibraryEvent()
|
||||||
|
|
||||||
|
fun recordJellyfinSync(summary: JellyfinSyncSummary)
|
||||||
|
|
||||||
|
fun recordJellyfinSyncFailure()
|
||||||
|
|
||||||
|
fun recordJellyfinUnmappedUser()
|
||||||
|
|
||||||
|
fun recordBackendWriteFailure()
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.project.movienight.application.ports.output
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
interface JellyfinEventRepositoryPort {
|
||||||
|
fun save(
|
||||||
|
eventId: String,
|
||||||
|
serverId: String?,
|
||||||
|
eventType: String,
|
||||||
|
occurredAt: OffsetDateTime?,
|
||||||
|
jellyfinUserId: String?,
|
||||||
|
jellyfinItemId: String?,
|
||||||
|
payload: String?,
|
||||||
|
): Int
|
||||||
|
|
||||||
|
fun delete(eventId: String)
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.project.movienight.application.ports.output
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.ContentType
|
||||||
|
|
||||||
|
data class JellyfinRemoteUser(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class JellyfinLibraryItemSnapshot(
|
||||||
|
val jellyfinItemId: String,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val contentType: ContentType,
|
||||||
|
val releaseYear: Int?,
|
||||||
|
val genres: List<String>,
|
||||||
|
val cast: List<String>,
|
||||||
|
val directors: List<String>,
|
||||||
|
val platformRating: Double?,
|
||||||
|
val imdbRating: Double?,
|
||||||
|
val externalUrl: String?,
|
||||||
|
val jellyfinLibraryId: String?,
|
||||||
|
val isPlayed: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface JellyfinSyncSourcePort {
|
||||||
|
fun fetchUsers(): List<JellyfinRemoteUser>
|
||||||
|
|
||||||
|
fun fetchLibraryItems(userId: String): List<JellyfinLibraryItemSnapshot>
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.project.movienight.application.services
|
package com.project.movienight.application.services
|
||||||
|
|
||||||
import com.project.movienight.adapters.metrics.BusinessMetricsService
|
|
||||||
import com.project.movienight.application.ports.input.AddFilmToLibraryCommand
|
import com.project.movienight.application.ports.input.AddFilmToLibraryCommand
|
||||||
import com.project.movienight.application.ports.input.AddFilmToLibraryUseCase
|
import com.project.movienight.application.ports.input.AddFilmToLibraryUseCase
|
||||||
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
|
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
|
||||||
@@ -12,6 +11,7 @@ import com.project.movienight.application.ports.input.MarkFilmViewedCommand
|
|||||||
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
|
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
|
||||||
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand
|
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand
|
||||||
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase
|
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase
|
||||||
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.IdGenerator
|
import com.project.movienight.application.ports.output.IdGenerator
|
||||||
import com.project.movienight.domain.exception.DomainException
|
import com.project.movienight.domain.exception.DomainException
|
||||||
@@ -25,7 +25,7 @@ import java.util.UUID
|
|||||||
class FilmLibraryService(
|
class FilmLibraryService(
|
||||||
private val filmLibraryRepository: FilmLibraryRepositoryPort,
|
private val filmLibraryRepository: FilmLibraryRepositoryPort,
|
||||||
private val idGenerator: IdGenerator,
|
private val idGenerator: IdGenerator,
|
||||||
private val businessMetricsService: BusinessMetricsService,
|
private val businessMetricsService: BusinessMetricsPort,
|
||||||
) : CreateFilmLibraryUseCase,
|
) : CreateFilmLibraryUseCase,
|
||||||
AddFilmToLibraryUseCase,
|
AddFilmToLibraryUseCase,
|
||||||
MarkFilmViewedUseCase,
|
MarkFilmViewedUseCase,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.project.movienight.application.services
|
package com.project.movienight.application.services
|
||||||
|
|
||||||
import com.project.movienight.adapters.metrics.BusinessMetricsService
|
|
||||||
import com.project.movienight.application.ports.input.GetFilmRatingsUseCase
|
import com.project.movienight.application.ports.input.GetFilmRatingsUseCase
|
||||||
import com.project.movienight.application.ports.input.RateFilmCommand
|
import com.project.movienight.application.ports.input.RateFilmCommand
|
||||||
import com.project.movienight.application.ports.input.RateFilmUseCase
|
import com.project.movienight.application.ports.input.RateFilmUseCase
|
||||||
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
import com.project.movienight.application.ports.output.FilmRatingRepositoryPort
|
import com.project.movienight.application.ports.output.FilmRatingRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.IdGenerator
|
import com.project.movienight.application.ports.output.IdGenerator
|
||||||
@@ -19,7 +19,7 @@ class FilmRatingService(
|
|||||||
private val filmRepository: FilmRepositoryPort,
|
private val filmRepository: FilmRepositoryPort,
|
||||||
private val filmRatingRepository: FilmRatingRepositoryPort,
|
private val filmRatingRepository: FilmRatingRepositoryPort,
|
||||||
private val idGenerator: IdGenerator,
|
private val idGenerator: IdGenerator,
|
||||||
private val businessMetricsService: BusinessMetricsService,
|
private val businessMetricsService: BusinessMetricsPort,
|
||||||
) : RateFilmUseCase,
|
) : RateFilmUseCase,
|
||||||
GetFilmRatingsUseCase {
|
GetFilmRatingsUseCase {
|
||||||
override fun rate(command: RateFilmCommand): FilmRating {
|
override fun rate(command: RateFilmCommand): FilmRating {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.project.movienight.application.services
|
package com.project.movienight.application.services
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.project.movienight.adapters.metrics.BusinessMetricsService
|
|
||||||
import com.project.movienight.adapters.persistence.jdbc.JellyfinEventRepository
|
|
||||||
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
|
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
|
||||||
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
|
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
|
||||||
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinEventRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.UserRepositoryPort
|
import com.project.movienight.application.ports.output.UserRepositoryPort
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -12,12 +12,12 @@ import java.time.OffsetDateTime
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class JellyfinEventService(
|
class JellyfinEventService(
|
||||||
private val jellyfinEventRepository: JellyfinEventRepository,
|
private val jellyfinEventRepository: JellyfinEventRepositoryPort,
|
||||||
private val userRepository: UserRepositoryPort,
|
private val userRepository: UserRepositoryPort,
|
||||||
private val filmRepository: FilmRepositoryPort,
|
private val filmRepository: FilmRepositoryPort,
|
||||||
private val markFilmViewedUseCase: MarkFilmViewedUseCase,
|
private val markFilmViewedUseCase: MarkFilmViewedUseCase,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val businessMetricsService: BusinessMetricsService,
|
private val businessMetricsService: BusinessMetricsPort,
|
||||||
) {
|
) {
|
||||||
private val playbackEventTypes = setOf("playback.ended", "playback.stopped", "playback.completed")
|
private val playbackEventTypes = setOf("playback.ended", "playback.stopped", "playback.completed")
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.project.movienight.application.services
|
package com.project.movienight.application.services
|
||||||
|
|
||||||
import com.project.movienight.adapters.jellyfin.JellyfinApiClient
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
import com.project.movienight.adapters.jellyfin.JellyfinLibraryItemSnapshot
|
|
||||||
import com.project.movienight.adapters.jellyfin.JellyfinRemoteUser
|
|
||||||
import com.project.movienight.adapters.metrics.BusinessMetricsService
|
|
||||||
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
||||||
import com.project.movienight.application.ports.output.IdGenerator
|
import com.project.movienight.application.ports.output.IdGenerator
|
||||||
import com.project.movienight.application.ports.output.JellyfinSyncStateRepositoryPort
|
import com.project.movienight.application.ports.output.JellyfinSyncStateRepositoryPort
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinLibraryItemSnapshot
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinRemoteUser
|
||||||
|
import com.project.movienight.application.ports.output.JellyfinSyncSourcePort
|
||||||
import com.project.movienight.application.ports.output.UserRepositoryPort
|
import com.project.movienight.application.ports.output.UserRepositoryPort
|
||||||
import com.project.movienight.config.JellyfinIntegrationProperties
|
import com.project.movienight.config.JellyfinIntegrationProperties
|
||||||
import com.project.movienight.domain.model.ContentType
|
import com.project.movienight.domain.model.ContentType
|
||||||
@@ -24,13 +24,13 @@ import java.time.LocalDateTime
|
|||||||
@Service
|
@Service
|
||||||
class JellyfinSyncService(
|
class JellyfinSyncService(
|
||||||
private val properties: JellyfinIntegrationProperties,
|
private val properties: JellyfinIntegrationProperties,
|
||||||
private val jellyfinApiClient: JellyfinApiClient,
|
private val jellyfinApiClient: JellyfinSyncSourcePort,
|
||||||
private val userRepository: UserRepositoryPort,
|
private val userRepository: UserRepositoryPort,
|
||||||
private val filmRepository: FilmRepositoryPort,
|
private val filmRepository: FilmRepositoryPort,
|
||||||
private val filmLibraryRepository: FilmLibraryRepositoryPort,
|
private val filmLibraryRepository: FilmLibraryRepositoryPort,
|
||||||
private val syncStateRepository: JellyfinSyncStateRepositoryPort,
|
private val syncStateRepository: JellyfinSyncStateRepositoryPort,
|
||||||
private val idGenerator: IdGenerator,
|
private val idGenerator: IdGenerator,
|
||||||
private val businessMetricsService: BusinessMetricsService,
|
private val businessMetricsService: BusinessMetricsPort,
|
||||||
) {
|
) {
|
||||||
@Scheduled(fixedDelayString = "\${integrations.jellyfin.sync-interval-ms:1800000}")
|
@Scheduled(fixedDelayString = "\${integrations.jellyfin.sync-interval-ms:1800000}")
|
||||||
fun scheduledSync() {
|
fun scheduledSync() {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.project.movienight.domain.model
|
|
||||||
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
data class RecommendationContext(
|
|
||||||
val userId: UUID,
|
|
||||||
val contentType: ContentType? = null,
|
|
||||||
val mood: String? = null,
|
|
||||||
val libraryOnly: Boolean = false,
|
|
||||||
val limit: Int = 10,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class RecommendationResult(
|
|
||||||
val film: Film,
|
|
||||||
val score: Double,
|
|
||||||
val reasons: List<String>,
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.project.movienight.domain.model
|
||||||
|
|
||||||
|
data class RecommendationResult(
|
||||||
|
val film: Film,
|
||||||
|
val score: Double,
|
||||||
|
val reasons: List<String>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.project.movienight.adapters.web
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
|
import org.springframework.test.web.servlet.get
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
|
class RecommendationControllerTest {
|
||||||
|
@Autowired
|
||||||
|
private lateinit var mockMvc: MockMvc
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid content type should return bad request`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/users/${UUID.randomUUID()}/recommendations") {
|
||||||
|
param("contentType", "INVALID")
|
||||||
|
}.andExpect {
|
||||||
|
status { isBadRequest() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package com.project.movienight.application.services
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.input.RecommendationQuery
|
||||||
|
import com.project.movienight.application.ports.output.BusinessMetricsPort
|
||||||
|
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
||||||
|
import com.project.movienight.application.ports.output.FilmRatingRepositoryPort
|
||||||
|
import com.project.movienight.application.ports.output.FilmRepositoryPort
|
||||||
|
import com.project.movienight.application.ports.output.UserPreferencesRepositoryPort
|
||||||
|
import com.project.movienight.domain.model.Film
|
||||||
|
import com.project.movienight.domain.model.FilmRating
|
||||||
|
import com.project.movienight.domain.model.UserPreferences
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class RecommendationServiceTest {
|
||||||
|
private val filmRepository: FilmRepositoryPort = mockk()
|
||||||
|
private val filmLibraryRepository: FilmLibraryRepositoryPort = mockk()
|
||||||
|
private val filmRatingRepository: FilmRatingRepositoryPort = mockk()
|
||||||
|
private val userPreferencesRepository: UserPreferencesRepositoryPort = mockk()
|
||||||
|
private val businessMetricsPort: BusinessMetricsPort = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val recommendationService =
|
||||||
|
RecommendationService(
|
||||||
|
filmRepository = filmRepository,
|
||||||
|
filmLibraryRepository = filmLibraryRepository,
|
||||||
|
filmRatingRepository = filmRatingRepository,
|
||||||
|
userPreferencesRepository = userPreferencesRepository,
|
||||||
|
businessMetricsService = businessMetricsPort,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should rank highly rated films above poorly rated films`() {
|
||||||
|
val userId = UUID.randomUUID()
|
||||||
|
val lowRatedFilmId = UUID.randomUUID()
|
||||||
|
val highRatedFilmId = UUID.randomUUID()
|
||||||
|
|
||||||
|
val lowRatedFilm = Film(id = lowRatedFilmId, title = "Low", description = "Low")
|
||||||
|
val highRatedFilm = Film(id = highRatedFilmId, title = "High", description = "High")
|
||||||
|
|
||||||
|
every { userPreferencesRepository.findByUserId(userId) } returns UserPreferences(userId = userId)
|
||||||
|
every { filmRatingRepository.findByUserId(userId) } returns
|
||||||
|
listOf(
|
||||||
|
FilmRating(id = UUID.randomUUID(), userId = userId, filmId = lowRatedFilmId, score = 1),
|
||||||
|
FilmRating(id = UUID.randomUUID(), userId = userId, filmId = highRatedFilmId, score = 10),
|
||||||
|
)
|
||||||
|
every { filmLibraryRepository.findAll() } returns emptyList()
|
||||||
|
every { filmRepository.findAll() } returns listOf(lowRatedFilm, highRatedFilm)
|
||||||
|
|
||||||
|
val result = recommendationService.recommend(RecommendationQuery(userId = userId))
|
||||||
|
|
||||||
|
assertEquals(highRatedFilmId, result.first().film.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user