This commit is contained in:
Elena
2026-05-22 15:38:28 +03:00
parent df8d3ee404
commit 9e9e2244ab
3 changed files with 305 additions and 252 deletions
@@ -1,168 +1,192 @@
package com.project.movienight.application.services //package com.project.movienight.application.services
//
import com.project.movienight.adapters.metrics.BusinessMetricsService //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.ListFilmLibraryEntriesUseCase
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.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
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
import com.project.movienight.domain.exception.EntityNotFoundException //import com.project.movienight.domain.exception.EntityNotFoundException
import com.project.movienight.domain.model.FilmLibrary //import com.project.movienight.domain.model.FilmLibrary
import org.slf4j.LoggerFactory //import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service //import org.springframework.stereotype.Service
import java.util.UUID //import java.util.UUID
//
@Service //@Service
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: BusinessMetricsService,
) : CreateFilmLibraryUseCase, //) : CreateFilmLibraryUseCase,
AddFilmToLibraryUseCase, // AddFilmToLibraryUseCase,
MarkFilmViewedUseCase, // MarkFilmViewedUseCase,
RemoveFilmFromLibraryUseCase, // RemoveFilmFromLibraryUseCase,
GetFilmLibraryUseCase, // GetFilmLibraryUseCase,
ListFilmLibraryEntriesUseCase { // ListFilmLibraryEntriesUseCase {
// private val log = LoggerFactory.getLogger(javaClass)
private val log = LoggerFactory.getLogger(javaClass) //
// override fun create(command: CreateFilmLibraryCommand): FilmLibrary {
override fun create(command: CreateFilmLibraryCommand): FilmLibrary { // log.info("Creating film library for user: {}", command.userId)
log.info("Creating film library for user: {}", command.userId) // log.debug("Create library request: userId={}, name={}", command.userId, command.name)
log.debug("Create library request: userId={}, name={}", command.userId, command.name) //
// val existing = findByUserId(command.userId)
val existing = findByUserId(command.userId) // if (existing != null) {
if (existing != null) { // log.debug("Library already exists for user {}: libraryId={}", command.userId, existing.id)
log.debug("Library already exists for user {}: libraryId={}", command.userId, existing.id) // return existing
return existing // }
} //
// log.warn("Library not found for user {}, cannot create", command.userId)
log.warn("Library not found for user {}, cannot create", command.userId) // throw EntityNotFoundException(entity = "Film library", id = command.userId.toString())
throw EntityNotFoundException(entity = "Film library", id = command.userId.toString()) // }
} //
// override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary {
override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary { // log.info("Adding film to library: userId={}, filmId={}", command.userId, command.filmId)
log.info("Adding film to library: userId={}, filmId={}", command.userId, command.filmId) //
// val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
val existingEntry = findByUserAndFilmId(command.userId, command.filmId) // if (existingEntry != null) {
if (existingEntry != null) { // log.debug("Film already in library, resetting as not viewed: entryId={}", existingEntry.id)
log.debug("Film already in library, resetting as not viewed: entryId={}", existingEntry.id) // val saved =
val saved = filmLibraryRepository.save( // filmLibraryRepository.save(
existingEntry.copy( // existingEntry.copy(
isViewed = false, // isViewed = false,
watchedAt = null, // watchedAt = null,
) // ),
) // )
businessMetricsService.recordLibraryEvent() // businessMetricsService.recordLibraryEvent()
log.info( // log.info(
"Film re-added to library: userId={}, filmId={}, entryId={}", // "Film re-added to library: userId={}, filmId={}, entryId={}",
command.userId, command.filmId, saved.id) // command.userId,
return saved // command.filmId,
} // saved.id,
// )
val saved = filmLibraryRepository.save( // return saved
FilmLibrary( // }
id = idGenerator.generateId(), //
userId = command.userId, // val saved =
filmId = command.filmId, // filmLibraryRepository.save(
comment = null, // FilmLibrary(
isViewed = false, // id = idGenerator.generateId(),
watchedAt = null, // userId = command.userId,
) // filmId = command.filmId,
) // comment = null,
businessMetricsService.recordLibraryEvent() // isViewed = false,
log.info("Film added to library: userId={}, filmId={}, entryId={}", command.userId, command.filmId, saved.id) // watchedAt = null,
return saved // ),
} // )
// businessMetricsService.recordLibraryEvent()
override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary { // log.info("Film added to library: userId={}, filmId={}, entryId={}", command.userId, command.filmId, saved.id)
log.info("Removing film from library: userId={}, filmId={}", command.userId, command.filmId) // return saved
// }
val existingLibrary = if (command.libraryId != null) { //
log.debug("Looking up by libraryId: {}", command.libraryId) // override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary {
filmLibraryRepository.findById(command.libraryId) // log.info("Removing film from library: userId={}, filmId={}", command.userId, command.filmId)
?: throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString()) //
} else { // val existingLibrary =
log.debug("Looking up by userId and filmId") // if (command.libraryId != null) {
findByUserAndFilmId(command.userId, command.filmId) // log.debug("Looking up by libraryId: {}", command.libraryId)
?: throw EntityNotFoundException(entity = "Film library", id = command.filmId.toString()) // filmLibraryRepository.findById(command.libraryId)
} // ?: throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString())
// } else {
if (existingLibrary.userId != command.userId || existingLibrary.filmId != command.filmId) { // log.debug("Looking up by userId and filmId")
log.warn("Film not found in user's library: userId={}, filmId={}", command.userId, command.filmId) // findByUserAndFilmId(command.userId, command.filmId)
throw DomainException("Film with id ${command.filmId} not found in user's library") // ?: throw EntityNotFoundException(entity = "Film library", id = command.filmId.toString())
} // }
//
filmLibraryRepository.deleteById(existingLibrary.id) // if (existingLibrary.userId != command.userId || existingLibrary.filmId != command.filmId) {
businessMetricsService.recordLibraryEvent() // log.warn("Film not found in user's library: userId={}, filmId={}", command.userId, command.filmId)
log.info( // throw DomainException("Film with id ${command.filmId} not found in user's library")
"Film removed from library: userId={}, filmId={}, entryId={}", // }
command.userId, command.filmId, existingLibrary.id) //
return existingLibrary // filmLibraryRepository.deleteById(existingLibrary.id)
} // businessMetricsService.recordLibraryEvent()
// log.info(
override fun markViewed(command: MarkFilmViewedCommand): FilmLibrary { // "Film removed from library: userId={}, filmId={}, entryId={}",
log.info("Marking film as viewed: userId={}, filmId={}", command.userId, command.filmId) // command.userId,
// command.filmId,
val existingEntry = findByUserAndFilmId(command.userId, command.filmId) // existingLibrary.id,
val watchedAt = command.watchedAt ?: java.time.LocalDateTime.now() // )
// return existingLibrary
log.debug("Marking as viewed at: {}", watchedAt) // }
//
val saved = if (existingEntry == null) { // override fun markViewed(command: MarkFilmViewedCommand): FilmLibrary {
log.debug("Film not in library, creating new entry as viewed") // log.info("Marking film as viewed: userId={}, filmId={}", command.userId, command.filmId)
filmLibraryRepository.save( //
FilmLibrary( // val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
id = idGenerator.generateId(), // val watchedAt = command.watchedAt ?: java.time.LocalDateTime.now()
userId = command.userId, //
filmId = command.filmId, // log.debug("Marking as viewed at: {}", watchedAt)
comment = null, //
isViewed = true, // val saved =
watchedAt = watchedAt, // if (existingEntry == null) {
) // log.debug("Film not in library, creating new entry as viewed")
) // filmLibraryRepository.save(
} else { // FilmLibrary(
log.debug("Updating existing entry: entryId={}, was viewed={}", existingEntry.id, existingEntry.isViewed) // id = idGenerator.generateId(),
filmLibraryRepository.save( // userId = command.userId,
existingEntry.copy( // filmId = command.filmId,
isViewed = true, // comment = null,
watchedAt = watchedAt, // isViewed = true,
) // watchedAt = watchedAt,
) // ),
} // )
businessMetricsService.recordLibraryEvent() // } else {
log.info("Film marked as viewed: userId={}, filmId={}, entryId={}", command.userId, command.filmId, saved.id) // log.debug(
return saved // "Updating existing entry: entryId={}, was viewed={}",
} // existingEntry.id,
// existingEntry.isViewed,
override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary { // )
log.debug("Getting library for user: {}", query.userId) // filmLibraryRepository.save(
val library = findByUserId(query.userId) // existingEntry.copy(
?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString()) // isViewed = true,
log.debug("Library found: userId={}, libraryId={}", query.userId, library.id) // watchedAt = watchedAt,
return library // ),
} // )
// }
override fun list(userId: UUID): List<FilmLibrary> { // businessMetricsService.recordLibraryEvent()
log.debug("Listing all library entries for user: {}", userId) // log.info(
val entries = filmLibraryRepository.findAll().filter { it.userId == userId } // "Film marked as viewed: userId={}, filmId={}, entryId={}",
log.info("User {} has {} films in library", userId, entries.size) // command.userId,
return entries // command.filmId,
} // saved.id,
// )
private fun findByUserId(userId: UUID): FilmLibrary? { // return saved
return filmLibraryRepository.findAll().firstOrNull { it.userId == userId } // }
} //
// override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary {
private fun findByUserAndFilmId(userId: UUID, filmId: UUID): FilmLibrary? { // log.debug("Getting library for user: {}", query.userId)
return filmLibraryRepository.findAll().firstOrNull { it.userId == userId && it.filmId == filmId } // val library =
} // findByUserId(query.userId)
} // ?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString())
// log.debug("Library found: userId={}, libraryId={}", query.userId, library.id)
// return library
// }
//
// override fun list(userId: UUID): List<FilmLibrary> {
// log.debug("Listing all library entries for user: {}", userId)
// val entries = filmLibraryRepository.findAll().filter { it.userId == userId }
// log.info("User {} has {} films in library", userId, entries.size)
// return entries
// }
//
// private fun findByUserId(userId: UUID): FilmLibrary? =
// filmLibraryRepository.findAll().firstOrNull {
// it.userId == userId
// }
//
// private fun findByUserAndFilmId(
// userId: UUID,
// filmId: UUID,
// ): FilmLibrary? =
// filmLibraryRepository.findAll().firstOrNull {
// it.userId == userId && it.filmId == filmId
// }
//}
@@ -33,13 +33,17 @@ class FilmService(
GetFilmByIdUseCase, GetFilmByIdUseCase,
GetAllFilmsUseCase, GetAllFilmsUseCase,
SearchFilmByTitleUseCase { SearchFilmByTitleUseCase {
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
override fun create(command: CreateFilmCommand): Film { override fun create(command: CreateFilmCommand): Film {
log.info("Creating new film: title='{}', contentType={}", command.title, command.contentType) log.info("Creating new film: title='{}', contentType={}", command.title, command.contentType)
log.debug("Create film request details: title='{}', descriptionLength={}, genres={}, releaseYear={}", log.debug(
command.title, command.description.length, command.genres, command.releaseYear) "Create film request details: title='{}', descriptionLength={}, genres={}, releaseYear={}",
command.title,
command.description.length,
command.genres,
command.releaseYear,
)
val sample = Timer.start(meterRegistry) val sample = Timer.start(meterRegistry)
@@ -55,21 +59,22 @@ class FilmService(
throw BlockedValueException(target = "Film", field = "description") throw BlockedValueException(target = "Film", field = "description")
} }
val film = Film( val film =
id = idGenerator.generateId(), Film(
title = command.title, id = idGenerator.generateId(),
description = command.description, title = command.title,
contentType = command.contentType, description = command.description,
releaseYear = command.releaseYear, contentType = command.contentType,
genres = command.genres, releaseYear = command.releaseYear,
cast = command.cast, genres = command.genres,
directors = command.directors, cast = command.cast,
imdbRating = command.imdbRating, directors = command.directors,
platformRating = command.platformRating, imdbRating = command.imdbRating,
externalUrl = command.externalUrl, platformRating = command.platformRating,
jellyfinItemId = command.jellyfinItemId, externalUrl = command.externalUrl,
jellyfinLibraryId = command.jellyfinLibraryId, jellyfinItemId = command.jellyfinItemId,
) jellyfinLibraryId = command.jellyfinLibraryId,
)
val saved = filmRepository.save(film) val saved = filmRepository.save(film)
filmCreatedCounter.increment() filmCreatedCounter.increment()
@@ -80,10 +85,18 @@ class FilmService(
} }
} }
override fun edit(id: UUID, command: EditFilmCommand): Film { override fun edit(
id: UUID,
command: EditFilmCommand,
): Film {
log.info("Editing film: id={}", id) log.info("Editing film: id={}", id)
log.debug("Edit film request details: id={}, title='{}', descriptionLength={}, genres={}", log.debug(
id, command.title, command.description.length, command.genres) "Edit film request details: id={}, title='{}', descriptionLength={}, genres={}",
id,
command.title,
command.description.length,
command.genres,
)
val sample = Timer.start(meterRegistry) val sample = Timer.start(meterRegistry)
@@ -108,20 +121,21 @@ class FilmService(
log.debug("Existing film found: id={}, current title='{}'", film.id, film.title) log.debug("Existing film found: id={}, current title='{}'", film.id, film.title)
film = film.copy( film =
title = command.title, film.copy(
description = command.description, title = command.title,
contentType = command.contentType, description = command.description,
releaseYear = command.releaseYear, contentType = command.contentType,
genres = command.genres, releaseYear = command.releaseYear,
cast = command.cast, genres = command.genres,
directors = command.directors, cast = command.cast,
imdbRating = command.imdbRating, directors = command.directors,
platformRating = command.platformRating, imdbRating = command.imdbRating,
externalUrl = command.externalUrl, platformRating = command.platformRating,
jellyfinItemId = command.jellyfinItemId, externalUrl = command.externalUrl,
jellyfinLibraryId = command.jellyfinLibraryId, jellyfinItemId = command.jellyfinItemId,
) jellyfinLibraryId = command.jellyfinLibraryId,
)
val saved = filmRepository.save(film) val saved = filmRepository.save(film)
filmEditedCounter.increment() filmEditedCounter.increment()
@@ -157,8 +171,9 @@ class FilmService(
override fun getById(id: UUID): Film { override fun getById(id: UUID): Film {
log.debug("Fetching film by id: {}", id) log.debug("Fetching film by id: {}", id)
val film = filmRepository.findById(id) val film =
?: throw EntityNotFoundException(entity = "Film", id = id.toString()) filmRepository.findById(id)
?: throw EntityNotFoundException(entity = "Film", id = id.toString())
log.debug("Film found: id={}, title='{}'", film.id, film.title) log.debug("Film found: id={}, title='{}'", film.id, film.title)
return film return film
} }
@@ -181,38 +196,45 @@ class FilmService(
return film return film
} }
private val filmCreatedCounter = Counter private val filmCreatedCounter =
.builder("film_created_total") Counter
.description("Total number of created films") .builder("film_created_total")
.register(meterRegistry) .description("Total number of created films")
.register(meterRegistry)
private val filmEditedCounter = Counter private val filmEditedCounter =
.builder("film_edited_total") Counter
.description("Total number of successfully edited films") .builder("film_edited_total")
.register(meterRegistry) .description("Total number of successfully edited films")
.register(meterRegistry)
private val filmDeletedCounter = Counter private val filmDeletedCounter =
.builder("film_deleted_total") Counter
.description("Total number of successfully deleted films") .builder("film_deleted_total")
.register(meterRegistry) .description("Total number of successfully deleted films")
.register(meterRegistry)
private val filmBlockedCounter = Counter private val filmBlockedCounter =
.builder("films.blocked") Counter
.description("Total blocked film operations") .builder("films.blocked")
.register(meterRegistry) .description("Total blocked film operations")
.register(meterRegistry)
private val createFilmTimer = Timer private val createFilmTimer =
.builder("films.create.duration") Timer
.description("Film creation duration") .builder("films.create.duration")
.register(meterRegistry) .description("Film creation duration")
.register(meterRegistry)
private val editFilmTimer = Timer private val editFilmTimer =
.builder("films.edit.duration") Timer
.description("Film edit duration") .builder("films.edit.duration")
.register(meterRegistry) .description("Film edit duration")
.register(meterRegistry)
private val deleteFilmTimer = Timer private val deleteFilmTimer =
.builder("films.delete.duration") Timer
.description("Film deletion duration") .builder("films.delete.duration")
.register(meterRegistry) .description("Film deletion duration")
.register(meterRegistry)
} }
@@ -27,7 +27,6 @@ class UserService(
DeleteUserUseCase, DeleteUserUseCase,
GetUserByIdUseCase, GetUserByIdUseCase,
GetAllUsersUseCase { GetAllUsersUseCase {
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
override fun create(command: CreateUserCommand): User { override fun create(command: CreateUserCommand): User {
@@ -39,20 +38,24 @@ class UserService(
throw BlockedValueException(target = "User", field = "name") throw BlockedValueException(target = "User", field = "name")
} }
val user = User( val user =
id = idGenerator.generateId(), User(
name = command.name, id = idGenerator.generateId(),
email = command.email, name = command.name,
library = null, email = command.email,
jellyfinUserId = null, library = null,
) jellyfinUserId = null,
)
val saved = userRepository.save(user) val saved = userRepository.save(user)
log.info("User created successfully: id={}, email='{}'", saved.id, saved.email) log.info("User created successfully: id={}, email='{}'", saved.id, saved.email)
return saved return saved
} }
override fun edit(id: UUID, command: EditUserCommand): User { override fun edit(
id: UUID,
command: EditUserCommand,
): User {
log.info("Editing user: id={}", id) log.info("Editing user: id={}", id)
log.debug("Edit user request: id={}, name='{}', jellyfinUserId={}", id, command.name, command.jellyfinUserId) log.debug("Edit user request: id={}, name='{}', jellyfinUserId={}", id, command.name, command.jellyfinUserId)
@@ -61,15 +64,17 @@ class UserService(
throw BlockedValueException(target = "User", field = "name") throw BlockedValueException(target = "User", field = "name")
} }
var user = userRepository.findById(id) var user =
?: throw EntityNotFoundException(entity = "User", id = id.toString()) userRepository.findById(id)
?: throw EntityNotFoundException(entity = "User", id = id.toString())
log.debug("Existing user found: id={}, current name='{}'", user.id, user.name) log.debug("Existing user found: id={}, current name='{}'", user.id, user.name)
user = user.copy( user =
name = command.name, user.copy(
jellyfinUserId = command.jellyfinUserId ?: user.jellyfinUserId, name = command.name,
) jellyfinUserId = command.jellyfinUserId ?: user.jellyfinUserId,
)
val saved = userRepository.save(user) val saved = userRepository.save(user)
log.info("User edited successfully: id={}, new name='{}'", saved.id, saved.name) log.info("User edited successfully: id={}, new name='{}'", saved.id, saved.name)
@@ -80,8 +85,9 @@ class UserService(
log.info("Deleting user: id={}", id) log.info("Deleting user: id={}", id)
log.debug("Delete user request: id={}", id) log.debug("Delete user request: id={}", id)
val user = userRepository.findById(id) val user =
?: throw EntityNotFoundException(entity = "User", id = id.toString()) userRepository.findById(id)
?: throw EntityNotFoundException(entity = "User", id = id.toString())
log.debug("User found for deletion: id={}, email='{}'", user.id, user.email) log.debug("User found for deletion: id={}, email='{}'", user.id, user.email)
@@ -91,8 +97,9 @@ class UserService(
override fun getById(id: UUID): User { override fun getById(id: UUID): User {
log.debug("Fetching user by id: {}", id) log.debug("Fetching user by id: {}", id)
val user = userRepository.findById(id) val user =
?: throw EntityNotFoundException(entity = "User", id = id.toString()) userRepository.findById(id)
?: throw EntityNotFoundException(entity = "User", id = id.toString())
log.debug("User found: id={}, name='{}', email='{}'", user.id, user.name, user.email) log.debug("User found: id={}, name='{}', email='{}'", user.id, user.name, user.email)
return user return user
} }