<type>(scope): <description>
[body] [footer(s)]
This commit is contained in:
@@ -3,8 +3,10 @@ package com.project.movienight
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
@ConfigurationPropertiesScan("com.project.movienight.config")
|
@ConfigurationPropertiesScan("com.project.movienight.config")
|
||||||
class MovieNightApplication
|
class MovieNightApplication
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ import org.springframework.web.bind.annotation.ResponseStatus
|
|||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
private fun String.toContentTypeOrFilm(): com.project.movienight.domain.model.ContentType =
|
||||||
|
runCatching {
|
||||||
|
com.project.movienight.domain.model.ContentType
|
||||||
|
.valueOf(this)
|
||||||
|
}.getOrDefault(com.project.movienight.domain.model.ContentType.FILM)
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/films")
|
@RequestMapping("/api/films")
|
||||||
class FilmController(
|
class FilmController(
|
||||||
@@ -45,6 +51,16 @@ class FilmController(
|
|||||||
CreateFilmCommand(
|
CreateFilmCommand(
|
||||||
title = request.title,
|
title = request.title,
|
||||||
description = request.description,
|
description = request.description,
|
||||||
|
contentType = request.contentType.toContentTypeOrFilm(),
|
||||||
|
releaseYear = request.releaseYear,
|
||||||
|
genres = request.genres,
|
||||||
|
cast = request.cast,
|
||||||
|
directors = request.directors,
|
||||||
|
imdbRating = request.imdbRating,
|
||||||
|
platformRating = request.platformRating,
|
||||||
|
externalUrl = request.externalUrl,
|
||||||
|
jellyfinItemId = request.jellyfinItemId,
|
||||||
|
jellyfinLibraryId = request.jellyfinLibraryId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -61,6 +77,16 @@ class FilmController(
|
|||||||
EditFilmCommand(
|
EditFilmCommand(
|
||||||
title = request.title,
|
title = request.title,
|
||||||
description = request.description,
|
description = request.description,
|
||||||
|
contentType = request.contentType.toContentTypeOrFilm(),
|
||||||
|
releaseYear = request.releaseYear,
|
||||||
|
genres = request.genres,
|
||||||
|
cast = request.cast,
|
||||||
|
directors = request.directors,
|
||||||
|
imdbRating = request.imdbRating,
|
||||||
|
platformRating = request.platformRating,
|
||||||
|
externalUrl = request.externalUrl,
|
||||||
|
jellyfinItemId = request.jellyfinItemId,
|
||||||
|
jellyfinLibraryId = request.jellyfinLibraryId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import com.project.movienight.application.ports.input.GetAllFilmsUseCase
|
|||||||
import com.project.movienight.application.ports.input.GetFilmByIdUseCase
|
import com.project.movienight.application.ports.input.GetFilmByIdUseCase
|
||||||
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
||||||
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
|
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
|
||||||
|
import com.project.movienight.application.ports.input.ListFilmLibraryEntriesUseCase
|
||||||
|
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
|
||||||
|
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.domain.exception.EntityNotFoundException
|
import com.project.movienight.domain.exception.EntityNotFoundException
|
||||||
@@ -30,10 +33,10 @@ import java.util.UUID
|
|||||||
class FilmLibraryController(
|
class FilmLibraryController(
|
||||||
private val createFilmLibraryUseCase: CreateFilmLibraryUseCase,
|
private val createFilmLibraryUseCase: CreateFilmLibraryUseCase,
|
||||||
private val addFilmToLibraryUseCase: AddFilmToLibraryUseCase,
|
private val addFilmToLibraryUseCase: AddFilmToLibraryUseCase,
|
||||||
|
private val markFilmViewedUseCase: MarkFilmViewedUseCase,
|
||||||
private val removeFilmFromLibraryUseCase: RemoveFilmFromLibraryUseCase,
|
private val removeFilmFromLibraryUseCase: RemoveFilmFromLibraryUseCase,
|
||||||
private val getFilmLibraryUseCase: GetFilmLibraryUseCase,
|
private val getFilmLibraryUseCase: GetFilmLibraryUseCase,
|
||||||
private val getFilmByIdUseCase: GetFilmByIdUseCase,
|
private val listFilmLibraryEntriesUseCase: ListFilmLibraryEntriesUseCase,
|
||||||
private val getAllFilmsUseCase: GetAllFilmsUseCase,
|
|
||||||
) {
|
) {
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
@@ -60,18 +63,10 @@ class FilmLibraryController(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@GetMapping("/films")
|
@GetMapping("/entries")
|
||||||
fun getAllFilmsInLibrary(
|
fun list(
|
||||||
@PathVariable userId: UUID,
|
@PathVariable userId: UUID,
|
||||||
): List<FilmResponse> {
|
): List<FilmLibraryResponse> = listFilmLibraryEntriesUseCase.list(userId).map { FilmLibraryResponse.fromDomain(it) }
|
||||||
val library =
|
|
||||||
getFilmLibraryUseCase.getLibrary(
|
|
||||||
GetFilmLibraryQuery(userId = userId),
|
|
||||||
)
|
|
||||||
|
|
||||||
val film = getFilmByIdUseCase.getById(library.filmId)
|
|
||||||
return listOf(FilmResponse.fromDomain(film))
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/films/{filmId}")
|
@PostMapping("/films/{filmId}")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
@@ -88,6 +83,20 @@ class FilmLibraryController(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@PostMapping("/films/{filmId}/viewed")
|
||||||
|
fun markViewed(
|
||||||
|
@PathVariable userId: UUID,
|
||||||
|
@PathVariable filmId: UUID,
|
||||||
|
): FilmLibraryResponse =
|
||||||
|
FilmLibraryResponse.fromDomain(
|
||||||
|
markFilmViewedUseCase.markViewed(
|
||||||
|
MarkFilmViewedCommand(
|
||||||
|
userId = userId,
|
||||||
|
filmId = filmId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@DeleteMapping("/films/{filmId}")
|
@DeleteMapping("/films/{filmId}")
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
fun removeFilm(
|
fun removeFilm(
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class UserController(
|
|||||||
command =
|
command =
|
||||||
EditUserCommand(
|
EditUserCommand(
|
||||||
name = request.name,
|
name = request.name,
|
||||||
|
jellyfinUserId = request.jellyfinUserId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,4 +3,14 @@ package com.project.movienight.adapters.web.dto.request
|
|||||||
data class CreateFilmRequest(
|
data class CreateFilmRequest(
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val contentType: String = "FILM",
|
||||||
|
val releaseYear: Int? = null,
|
||||||
|
val genres: List<String> = emptyList(),
|
||||||
|
val cast: List<String> = emptyList(),
|
||||||
|
val directors: List<String> = emptyList(),
|
||||||
|
val imdbRating: Double? = null,
|
||||||
|
val platformRating: Double? = null,
|
||||||
|
val externalUrl: String? = null,
|
||||||
|
val jellyfinItemId: String? = null,
|
||||||
|
val jellyfinLibraryId: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,4 +3,14 @@ package com.project.movienight.adapters.web.dto.request
|
|||||||
data class EditFilmRequest(
|
data class EditFilmRequest(
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val contentType: String = "FILM",
|
||||||
|
val releaseYear: Int? = null,
|
||||||
|
val genres: List<String> = emptyList(),
|
||||||
|
val cast: List<String> = emptyList(),
|
||||||
|
val directors: List<String> = emptyList(),
|
||||||
|
val imdbRating: Double? = null,
|
||||||
|
val platformRating: Double? = null,
|
||||||
|
val externalUrl: String? = null,
|
||||||
|
val jellyfinItemId: String? = null,
|
||||||
|
val jellyfinLibraryId: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ package com.project.movienight.adapters.web.dto.request
|
|||||||
|
|
||||||
data class EditUserRequest(
|
data class EditUserRequest(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val jellyfinUserId: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
+2
@@ -9,6 +9,7 @@ data class FilmLibraryResponse(
|
|||||||
val filmId: UUID,
|
val filmId: UUID,
|
||||||
val comment: String?,
|
val comment: String?,
|
||||||
val isViewed: Boolean,
|
val isViewed: Boolean,
|
||||||
|
val watchedAt: java.time.LocalDateTime?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromDomain(filmLibrary: FilmLibrary): FilmLibraryResponse =
|
fun fromDomain(filmLibrary: FilmLibrary): FilmLibraryResponse =
|
||||||
@@ -18,6 +19,7 @@ data class FilmLibraryResponse(
|
|||||||
filmId = filmLibrary.filmId,
|
filmId = filmLibrary.filmId,
|
||||||
comment = filmLibrary.comment,
|
comment = filmLibrary.comment,
|
||||||
isViewed = filmLibrary.isViewed,
|
isViewed = filmLibrary.isViewed,
|
||||||
|
watchedAt = filmLibrary.watchedAt,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.project.movienight.adapters.web.dto.response
|
package com.project.movienight.adapters.web.dto.response
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.ContentType
|
||||||
import com.project.movienight.domain.model.Film
|
import com.project.movienight.domain.model.Film
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -7,6 +8,16 @@ data class FilmResponse(
|
|||||||
val id: UUID,
|
val id: UUID,
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val contentType: ContentType,
|
||||||
|
val releaseYear: Int?,
|
||||||
|
val genres: List<String>,
|
||||||
|
val cast: List<String>,
|
||||||
|
val directors: List<String>,
|
||||||
|
val imdbRating: Double?,
|
||||||
|
val platformRating: Double?,
|
||||||
|
val externalUrl: String?,
|
||||||
|
val jellyfinItemId: String?,
|
||||||
|
val jellyfinLibraryId: String?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromDomain(film: Film): FilmResponse =
|
fun fromDomain(film: Film): FilmResponse =
|
||||||
@@ -14,6 +25,16 @@ data class FilmResponse(
|
|||||||
id = film.id,
|
id = film.id,
|
||||||
title = film.title,
|
title = film.title,
|
||||||
description = film.description,
|
description = film.description,
|
||||||
|
contentType = film.contentType,
|
||||||
|
releaseYear = film.releaseYear,
|
||||||
|
genres = film.genres,
|
||||||
|
cast = film.cast,
|
||||||
|
directors = film.directors,
|
||||||
|
imdbRating = film.imdbRating,
|
||||||
|
platformRating = film.platformRating,
|
||||||
|
externalUrl = film.externalUrl,
|
||||||
|
jellyfinItemId = film.jellyfinItemId,
|
||||||
|
jellyfinLibraryId = film.jellyfinLibraryId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ data class UserResponse(
|
|||||||
val id: UUID,
|
val id: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val email: String,
|
val email: String,
|
||||||
|
val jellyfinUserId: String?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromDomain(user: User): UserResponse =
|
fun fromDomain(user: User): UserResponse =
|
||||||
@@ -14,6 +15,7 @@ data class UserResponse(
|
|||||||
id = user.id,
|
id = user.id,
|
||||||
name = user.name,
|
name = user.name,
|
||||||
email = user.email,
|
email = user.email,
|
||||||
|
jellyfinUserId = user.jellyfinUserId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.project.movienight.application.ports.input
|
package com.project.movienight.application.ports.input
|
||||||
|
|
||||||
import com.project.movienight.domain.model.FilmLibrary
|
import com.project.movienight.domain.model.FilmLibrary
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface CreateFilmLibraryUseCase {
|
interface CreateFilmLibraryUseCase {
|
||||||
@@ -21,6 +22,16 @@ data class AddFilmToLibraryCommand(
|
|||||||
val filmId: UUID,
|
val filmId: UUID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface MarkFilmViewedUseCase {
|
||||||
|
fun markViewed(command: MarkFilmViewedCommand): FilmLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MarkFilmViewedCommand(
|
||||||
|
val userId: UUID,
|
||||||
|
val filmId: UUID,
|
||||||
|
val watchedAt: LocalDateTime? = null,
|
||||||
|
)
|
||||||
|
|
||||||
interface RemoveFilmFromLibraryUseCase {
|
interface RemoveFilmFromLibraryUseCase {
|
||||||
fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary
|
fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary
|
||||||
}
|
}
|
||||||
@@ -38,3 +49,7 @@ interface GetFilmLibraryUseCase {
|
|||||||
data class GetFilmLibraryQuery(
|
data class GetFilmLibraryQuery(
|
||||||
val userId: UUID,
|
val userId: UUID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface ListFilmLibraryEntriesUseCase {
|
||||||
|
fun list(userId: UUID): List<FilmLibrary>
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.project.movienight.application.ports.input
|
package com.project.movienight.application.ports.input
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.ContentType
|
||||||
import com.project.movienight.domain.model.Film
|
import com.project.movienight.domain.model.Film
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -10,6 +11,16 @@ interface CreateFilmUseCase {
|
|||||||
data class CreateFilmCommand(
|
data class CreateFilmCommand(
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val contentType: ContentType = ContentType.FILM,
|
||||||
|
val releaseYear: Int? = null,
|
||||||
|
val genres: List<String> = emptyList(),
|
||||||
|
val cast: List<String> = emptyList(),
|
||||||
|
val directors: List<String> = emptyList(),
|
||||||
|
val imdbRating: Double? = null,
|
||||||
|
val platformRating: Double? = null,
|
||||||
|
val externalUrl: String? = null,
|
||||||
|
val jellyfinItemId: String? = null,
|
||||||
|
val jellyfinLibraryId: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface EditFilmUseCase {
|
interface EditFilmUseCase {
|
||||||
@@ -22,6 +33,16 @@ interface EditFilmUseCase {
|
|||||||
data class EditFilmCommand(
|
data class EditFilmCommand(
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val contentType: ContentType = ContentType.FILM,
|
||||||
|
val releaseYear: Int? = null,
|
||||||
|
val genres: List<String> = emptyList(),
|
||||||
|
val cast: List<String> = emptyList(),
|
||||||
|
val directors: List<String> = emptyList(),
|
||||||
|
val imdbRating: Double? = null,
|
||||||
|
val platformRating: Double? = null,
|
||||||
|
val externalUrl: String? = null,
|
||||||
|
val jellyfinItemId: String? = null,
|
||||||
|
val jellyfinLibraryId: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface DeleteFilmUseCase {
|
interface DeleteFilmUseCase {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface EditUserUseCase {
|
|||||||
|
|
||||||
data class EditUserCommand(
|
data class EditUserCommand(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val jellyfinUserId: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface DeleteUserUseCase {
|
interface DeleteUserUseCase {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
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
|
||||||
import com.project.movienight.application.ports.input.CreateFilmLibraryUseCase
|
import com.project.movienight.application.ports.input.CreateFilmLibraryUseCase
|
||||||
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
||||||
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
|
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
|
||||||
|
import com.project.movienight.application.ports.input.ListFilmLibraryEntriesUseCase
|
||||||
|
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
|
||||||
|
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.FilmLibraryRepositoryPort
|
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
|
||||||
@@ -20,70 +24,119 @@ 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,
|
||||||
) : CreateFilmLibraryUseCase,
|
) : CreateFilmLibraryUseCase,
|
||||||
AddFilmToLibraryUseCase,
|
AddFilmToLibraryUseCase,
|
||||||
|
MarkFilmViewedUseCase,
|
||||||
RemoveFilmFromLibraryUseCase,
|
RemoveFilmFromLibraryUseCase,
|
||||||
GetFilmLibraryUseCase {
|
GetFilmLibraryUseCase,
|
||||||
|
ListFilmLibraryEntriesUseCase {
|
||||||
override fun create(command: CreateFilmLibraryCommand): FilmLibrary {
|
override fun create(command: CreateFilmLibraryCommand): FilmLibrary {
|
||||||
val existingLibrary = findByUserId(command.userId)
|
findByUserId(command.userId)?.let { return it }
|
||||||
if (existingLibrary != null) {
|
|
||||||
return existingLibrary
|
|
||||||
}
|
|
||||||
|
|
||||||
return filmLibraryRepository.save(
|
val libraryId = idGenerator.generateId()
|
||||||
|
val saved =
|
||||||
|
filmLibraryRepository.save(
|
||||||
FilmLibrary(
|
FilmLibrary(
|
||||||
id = idGenerator.generateId(),
|
id = libraryId,
|
||||||
userId = command.userId,
|
userId = command.userId,
|
||||||
filmId = idGenerator.generateId(),
|
filmId = libraryId,
|
||||||
comment = command.name,
|
comment = command.name,
|
||||||
isViewed = false,
|
isViewed = false,
|
||||||
|
watchedAt = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
businessMetricsService.recordLibraryEvent()
|
||||||
|
return saved
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary {
|
override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary {
|
||||||
val existingLibrary = findByUserId(command.userId)
|
val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
|
||||||
if (existingLibrary == null) {
|
if (existingEntry != null) {
|
||||||
return filmLibraryRepository.save(
|
val saved =
|
||||||
|
filmLibraryRepository.save(
|
||||||
|
existingEntry.copy(
|
||||||
|
isViewed = false,
|
||||||
|
watchedAt = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
businessMetricsService.recordLibraryEvent()
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
val saved =
|
||||||
|
filmLibraryRepository.save(
|
||||||
FilmLibrary(
|
FilmLibrary(
|
||||||
id = idGenerator.generateId(),
|
id = idGenerator.generateId(),
|
||||||
userId = command.userId,
|
userId = command.userId,
|
||||||
filmId = command.filmId,
|
filmId = command.filmId,
|
||||||
comment = null,
|
comment = null,
|
||||||
isViewed = false,
|
isViewed = false,
|
||||||
|
watchedAt = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
businessMetricsService.recordLibraryEvent()
|
||||||
|
return saved
|
||||||
return filmLibraryRepository.save(
|
|
||||||
existingLibrary.copy(
|
|
||||||
filmId = command.filmId,
|
|
||||||
isViewed = false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary {
|
override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary {
|
||||||
val existingLibrary =
|
val existingLibrary =
|
||||||
findByUserId(command.userId)
|
if (command.libraryId != null) {
|
||||||
?: throw EntityNotFoundException(entity = "Film library", id = command.userId.toString())
|
filmLibraryRepository.findById(command.libraryId)
|
||||||
|
?: throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString())
|
||||||
if (command.libraryId != null && command.libraryId != existingLibrary.id) {
|
} else {
|
||||||
throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString())
|
findByUserAndFilmId(command.userId, command.filmId)
|
||||||
|
?: throw EntityNotFoundException(entity = "Film library", id = command.filmId.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingLibrary.filmId != command.filmId) {
|
if (existingLibrary.userId != command.userId || existingLibrary.filmId != command.filmId) {
|
||||||
throw DomainException("Film with id ${command.filmId} not found in user's library")
|
throw DomainException("Film with id ${command.filmId} not found in user's library")
|
||||||
}
|
}
|
||||||
|
|
||||||
filmLibraryRepository.deleteById(existingLibrary.id)
|
filmLibraryRepository.deleteById(existingLibrary.id)
|
||||||
|
businessMetricsService.recordLibraryEvent()
|
||||||
return existingLibrary
|
return existingLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun markViewed(command: MarkFilmViewedCommand): FilmLibrary {
|
||||||
|
val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
|
||||||
|
val watchedAt = command.watchedAt ?: java.time.LocalDateTime.now()
|
||||||
|
|
||||||
|
val saved =
|
||||||
|
if (existingEntry == null) {
|
||||||
|
filmLibraryRepository.save(
|
||||||
|
FilmLibrary(
|
||||||
|
id = idGenerator.generateId(),
|
||||||
|
userId = command.userId,
|
||||||
|
filmId = command.filmId,
|
||||||
|
comment = null,
|
||||||
|
isViewed = true,
|
||||||
|
watchedAt = watchedAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
filmLibraryRepository.save(
|
||||||
|
existingEntry.copy(
|
||||||
|
isViewed = true,
|
||||||
|
watchedAt = watchedAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
businessMetricsService.recordLibraryEvent()
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary =
|
override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary =
|
||||||
findByUserId(query.userId)
|
findByUserId(query.userId)
|
||||||
?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString())
|
?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString())
|
||||||
|
|
||||||
|
override fun list(userId: UUID): List<FilmLibrary> = filmLibraryRepository.findAll().filter { it.userId == userId }
|
||||||
|
|
||||||
private fun findByUserId(userId: UUID): FilmLibrary? =
|
private fun findByUserId(userId: UUID): FilmLibrary? =
|
||||||
filmLibraryRepository.findAll().firstOrNull { it.userId == userId }
|
filmLibraryRepository.findAll().firstOrNull { it.userId == userId }
|
||||||
|
|
||||||
|
private fun findByUserAndFilmId(
|
||||||
|
userId: UUID,
|
||||||
|
filmId: UUID,
|
||||||
|
): FilmLibrary? = filmLibraryRepository.findAll().firstOrNull { it.userId == userId && it.filmId == filmId }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,16 +61,18 @@ class FilmService(
|
|||||||
id = idGenerator.generateId(),
|
id = idGenerator.generateId(),
|
||||||
title = command.title,
|
title = command.title,
|
||||||
description = command.description,
|
description = command.description,
|
||||||
|
contentType = command.contentType,
|
||||||
|
releaseYear = command.releaseYear,
|
||||||
|
genres = command.genres,
|
||||||
|
cast = command.cast,
|
||||||
|
directors = command.directors,
|
||||||
|
imdbRating = command.imdbRating,
|
||||||
|
platformRating = command.platformRating,
|
||||||
|
externalUrl = command.externalUrl,
|
||||||
|
jellyfinItemId = command.jellyfinItemId,
|
||||||
|
jellyfinLibraryId = command.jellyfinLibraryId,
|
||||||
)
|
)
|
||||||
val saved = filmRepository.save(film)
|
return filmRepository.save(film)
|
||||||
|
|
||||||
filmCreatedCounter.increment()
|
|
||||||
|
|
||||||
log.info("Film created: id='{}', title='{}'", saved.id, saved.title)
|
|
||||||
return saved
|
|
||||||
} finally {
|
|
||||||
sample.stop(createFilmTimer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun edit(
|
override fun edit(
|
||||||
@@ -100,20 +102,23 @@ class FilmService(
|
|||||||
throw EntityNotFoundException(entity = "Film", id = id.toString())
|
throw EntityNotFoundException(entity = "Film", id = id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatedFilm =
|
film =
|
||||||
film.copy(
|
film.copy(
|
||||||
title = command.title,
|
title = command.title,
|
||||||
description = command.description,
|
description = command.description,
|
||||||
|
contentType = command.contentType,
|
||||||
|
releaseYear = command.releaseYear,
|
||||||
|
genres = command.genres,
|
||||||
|
cast = command.cast,
|
||||||
|
directors = command.directors,
|
||||||
|
imdbRating = command.imdbRating,
|
||||||
|
platformRating = command.platformRating,
|
||||||
|
externalUrl = command.externalUrl,
|
||||||
|
jellyfinItemId = command.jellyfinItemId,
|
||||||
|
jellyfinLibraryId = command.jellyfinLibraryId,
|
||||||
)
|
)
|
||||||
val saved = filmRepository.save(updatedFilm)
|
|
||||||
|
|
||||||
filmEditedCounter.increment()
|
return filmRepository.save(film)
|
||||||
|
|
||||||
log.info("Film edited: id='{}'", saved.id)
|
|
||||||
return saved
|
|
||||||
} finally {
|
|
||||||
sample.stop(editFilmTimer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(id: UUID) {
|
override fun delete(id: UUID) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class UserService(
|
|||||||
name = command.name,
|
name = command.name,
|
||||||
email = command.email,
|
email = command.email,
|
||||||
library = null,
|
library = null,
|
||||||
|
jellyfinUserId = null,
|
||||||
)
|
)
|
||||||
return userRepository.save(user)
|
return userRepository.save(user)
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,13 @@ class UserService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user = userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString())
|
var user = userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString())
|
||||||
user = user.copy(name = command.name)
|
|
||||||
|
user =
|
||||||
|
user.copy(
|
||||||
|
name = command.name,
|
||||||
|
jellyfinUserId = command.jellyfinUserId ?: user.jellyfinUserId,
|
||||||
|
)
|
||||||
|
|
||||||
return userRepository.save(user)
|
return userRepository.save(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ info:
|
|||||||
description: MovieNight backend service
|
description: MovieNight backend service
|
||||||
version: ${project.version:unknown}
|
version: ${project.version:unknown}
|
||||||
|
|
||||||
|
integrations:
|
||||||
|
jellyfin:
|
||||||
|
enabled: ${JELLYFIN_SYNC_ENABLED:false}
|
||||||
|
base-url: ${JELLYFIN_BASE_URL:}
|
||||||
|
api-key: ${JELLYFIN_API_KEY:}
|
||||||
|
sync-interval-ms: ${JELLYFIN_SYNC_INTERVAL_MS:1800000}
|
||||||
|
request-timeout-ms: ${JELLYFIN_REQUEST_TIMEOUT_MS:20000}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
user:
|
user:
|
||||||
blocked-names:
|
blocked-names:
|
||||||
|
|||||||
+23
-13
@@ -1,5 +1,6 @@
|
|||||||
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.CreateFilmLibraryCommand
|
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
|
||||||
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
|
||||||
@@ -23,32 +24,33 @@ import java.util.UUID
|
|||||||
class FilmLibraryServiceTest {
|
class FilmLibraryServiceTest {
|
||||||
private lateinit var filmLibraryRepository: FilmLibraryRepositoryPort
|
private lateinit var filmLibraryRepository: FilmLibraryRepositoryPort
|
||||||
private lateinit var idGenerator: IdGenerator
|
private lateinit var idGenerator: IdGenerator
|
||||||
|
private lateinit var businessMetricsService: BusinessMetricsService
|
||||||
private lateinit var filmLibraryService: FilmLibraryService
|
private lateinit var filmLibraryService: FilmLibraryService
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
filmLibraryRepository = mockk()
|
filmLibraryRepository = mockk()
|
||||||
idGenerator = mockk()
|
idGenerator = mockk()
|
||||||
filmLibraryService = FilmLibraryService(filmLibraryRepository, idGenerator)
|
businessMetricsService = mockk(relaxed = true)
|
||||||
|
filmLibraryService = FilmLibraryService(filmLibraryRepository, idGenerator, businessMetricsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should create new film library when user has no library`() {
|
fun `should create new film library when user has no library`() {
|
||||||
val userId = UUID.randomUUID()
|
val userId = UUID.randomUUID()
|
||||||
val libraryId = UUID.randomUUID()
|
val libraryId = UUID.randomUUID()
|
||||||
val filmId = UUID.randomUUID()
|
|
||||||
val command = CreateFilmLibraryCommand(userId = userId, name = "My Films")
|
val command = CreateFilmLibraryCommand(userId = userId, name = "My Films")
|
||||||
val expectedLibrary =
|
val expectedLibrary =
|
||||||
FilmLibrary(
|
FilmLibrary(
|
||||||
id = libraryId,
|
id = libraryId,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
filmId = filmId,
|
filmId = libraryId,
|
||||||
comment = "My Films",
|
comment = "My Films",
|
||||||
isViewed = false,
|
isViewed = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
every { filmLibraryRepository.findAll() } returns emptyList()
|
every { filmLibraryRepository.findAll() } returns emptyList()
|
||||||
every { idGenerator.generateId() } returnsMany listOf(libraryId, filmId)
|
every { idGenerator.generateId() } returns libraryId
|
||||||
every {
|
every {
|
||||||
filmLibraryRepository.save(
|
filmLibraryRepository.save(
|
||||||
match {
|
match {
|
||||||
@@ -62,11 +64,11 @@ class FilmLibraryServiceTest {
|
|||||||
assertNotNull(result)
|
assertNotNull(result)
|
||||||
assertEquals(libraryId, result.id)
|
assertEquals(libraryId, result.id)
|
||||||
assertEquals(userId, result.userId)
|
assertEquals(userId, result.userId)
|
||||||
assertEquals(filmId, result.filmId)
|
assertEquals(libraryId, result.filmId)
|
||||||
assertEquals("My Films", result.comment)
|
assertEquals("My Films", result.comment)
|
||||||
|
|
||||||
verify(exactly = 1) { filmLibraryRepository.findAll() }
|
verify(exactly = 1) { filmLibraryRepository.findAll() }
|
||||||
verify(exactly = 2) { idGenerator.generateId() }
|
verify(exactly = 1) { idGenerator.generateId() }
|
||||||
verify(exactly = 1) { filmLibraryRepository.save(any()) }
|
verify(exactly = 1) { filmLibraryRepository.save(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +133,7 @@ class FilmLibraryServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should add film to existing library`() {
|
fun `should add film as a new library entry when another film already exists`() {
|
||||||
val userId = UUID.randomUUID()
|
val userId = UUID.randomUUID()
|
||||||
val oldFilmId = UUID.randomUUID()
|
val oldFilmId = UUID.randomUUID()
|
||||||
val newFilmId = UUID.randomUUID()
|
val newFilmId = UUID.randomUUID()
|
||||||
@@ -144,16 +146,24 @@ class FilmLibraryServiceTest {
|
|||||||
isViewed = true,
|
isViewed = true,
|
||||||
)
|
)
|
||||||
val command = AddFilmToLibraryCommand(userId = userId, filmId = newFilmId)
|
val command = AddFilmToLibraryCommand(userId = userId, filmId = newFilmId)
|
||||||
val updatedLibrary = existingLibrary.copy(filmId = newFilmId, isViewed = false)
|
val createdLibrary =
|
||||||
|
FilmLibrary(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
userId = userId,
|
||||||
|
filmId = newFilmId,
|
||||||
|
comment = null,
|
||||||
|
isViewed = false,
|
||||||
|
)
|
||||||
|
|
||||||
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
|
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
|
||||||
|
every { idGenerator.generateId() } returns createdLibrary.id
|
||||||
every {
|
every {
|
||||||
filmLibraryRepository.save(
|
filmLibraryRepository.save(
|
||||||
match {
|
match {
|
||||||
it.filmId == newFilmId && it.isViewed == false
|
it.id == createdLibrary.id && it.userId == userId && it.filmId == newFilmId && it.isViewed == false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} returns updatedLibrary
|
} returns createdLibrary
|
||||||
|
|
||||||
val result = filmLibraryService.addFilm(command)
|
val result = filmLibraryService.addFilm(command)
|
||||||
|
|
||||||
@@ -161,7 +171,7 @@ class FilmLibraryServiceTest {
|
|||||||
assertEquals(false, result.isViewed)
|
assertEquals(false, result.isViewed)
|
||||||
|
|
||||||
verify(exactly = 1) { filmLibraryRepository.findAll() }
|
verify(exactly = 1) { filmLibraryRepository.findAll() }
|
||||||
verify(exactly = 0) { idGenerator.generateId() }
|
verify(exactly = 1) { idGenerator.generateId() }
|
||||||
verify(exactly = 1) { filmLibraryRepository.save(any()) }
|
verify(exactly = 1) { filmLibraryRepository.save(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,13 +263,13 @@ class FilmLibraryServiceTest {
|
|||||||
libraryId = wrongLibraryId,
|
libraryId = wrongLibraryId,
|
||||||
)
|
)
|
||||||
|
|
||||||
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
|
every { filmLibraryRepository.findById(wrongLibraryId) } returns null
|
||||||
|
|
||||||
assertThrows<EntityNotFoundException> {
|
assertThrows<EntityNotFoundException> {
|
||||||
filmLibraryService.removeFilm(command)
|
filmLibraryService.removeFilm(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(exactly = 1) { filmLibraryRepository.findAll() }
|
verify(exactly = 1) { filmLibraryRepository.findById(wrongLibraryId) }
|
||||||
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
|
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user