Skip to content

Commit

Permalink
feat: add total unseen minutes (missed episodes)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Sep 19, 2024
1 parent 6161cab commit ea42922
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fr.shikkanime.controllers.admin

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.TokenDto
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.Anime
import fr.shikkanime.entities.EpisodeMapping
import fr.shikkanime.entities.EpisodeVariant
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fr.shikkanime.controllers.api
import com.google.inject.Inject
import fr.shikkanime.dtos.AllFollowedEpisodeDto
import fr.shikkanime.dtos.GenericDto
import fr.shikkanime.dtos.member.RefreshMemberDto
import fr.shikkanime.services.ImageService
import fr.shikkanime.services.MemberFollowAnimeService
import fr.shikkanime.services.MemberFollowEpisodeService
Expand All @@ -11,6 +12,7 @@ import fr.shikkanime.services.caches.MemberCacheService
import fr.shikkanime.utils.StringUtils
import fr.shikkanime.utils.routes.*
import fr.shikkanime.utils.routes.method.Delete
import fr.shikkanime.utils.routes.method.Get
import fr.shikkanime.utils.routes.method.Post
import fr.shikkanime.utils.routes.method.Put
import fr.shikkanime.utils.routes.openapi.OpenAPI
Expand Down Expand Up @@ -241,4 +243,19 @@ class MemberController {

return Response.ok()
}

@Path("/refresh")
@Get
@JWTAuthenticated
@OpenAPI(
description = "Get member data after a watchlist modification",
responses = [
OpenAPIResponse(200, "Member data refreshed", RefreshMemberDto::class),
OpenAPIResponse(401, "Unauthorized")
],
security = true
)
private fun getRefreshMember(@JWTUser memberUuid: UUID): Response {
return Response.ok(memberCacheService.getRefreshMember(memberUuid))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package fr.shikkanime.converters.member

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.MemberDto
import fr.shikkanime.dtos.TokenDto
import fr.shikkanime.dtos.member.MemberDto
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.Member
import fr.shikkanime.services.ImageService
import fr.shikkanime.services.MemberFollowAnimeService
Expand All @@ -19,6 +19,7 @@ class MemberToMemberDtoConverter : AbstractConverter<Member, MemberDto>() {

override fun convert(from: Member): MemberDto {
val tokenDto = convert(from, TokenDto::class.java)
val seenAndUnseenDuration = memberFollowEpisodeService.getSeenAndUnseenDuration(from)

return MemberDto(
uuid = from.uuid!!,
Expand All @@ -29,7 +30,8 @@ class MemberToMemberDtoConverter : AbstractConverter<Member, MemberDto>() {
email = from.email,
followedAnimes = memberFollowAnimeService.findAllFollowedAnimesUUID(from),
followedEpisodes = memberFollowEpisodeService.findAllFollowedEpisodesUUID(from),
totalDuration = memberFollowEpisodeService.getTotalDuration(from),
totalDuration = seenAndUnseenDuration.first,
totalUnseenDuration = seenAndUnseenDuration.second,
hasProfilePicture = ImageService[from.uuid, ImageService.Type.IMAGE] != null
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package fr.shikkanime.converters.member

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.MissedAnimeDto
import fr.shikkanime.dtos.PageableDto
import fr.shikkanime.dtos.mappings.EpisodeMappingDto
import fr.shikkanime.dtos.member.RefreshMemberDto
import fr.shikkanime.entities.Member
import fr.shikkanime.services.MemberFollowAnimeService
import fr.shikkanime.services.MemberFollowEpisodeService

class MemberToRefreshMemberDtoConverter : AbstractConverter<Member, RefreshMemberDto>() {
@Inject
private lateinit var memberFollowAnimeService: MemberFollowAnimeService

@Inject
private lateinit var memberFollowEpisodeService: MemberFollowEpisodeService

override fun convert(from: Member): RefreshMemberDto {
val page = 1
val limit = 9

val missedAnimesPageable = memberFollowAnimeService.findAllMissedAnimes(from, page, limit)
val followedAnimesPageable = memberFollowAnimeService.findAllFollowedAnimes(from, page, limit)
val followedEpisodesPageable = memberFollowEpisodeService.findAllFollowedEpisodes(from, page, limit)
val (totalDuration, totalUnseenDuration) = memberFollowEpisodeService.getSeenAndUnseenDuration(from)

val missedAnimeDtos = missedAnimesPageable.data.map { tuple ->
MissedAnimeDto(
convert(tuple[0], AnimeDto::class.java),
tuple[1] as Long
)
}

return RefreshMemberDto(
missedAnimes = PageableDto(
data = missedAnimeDtos,
page = missedAnimesPageable.page,
limit = missedAnimesPageable.limit,
total = missedAnimesPageable.total,
),
followedAnimes = PageableDto.fromPageable(followedAnimesPageable, AnimeDto::class.java),
followedEpisodes = PageableDto.fromPageable(followedEpisodesPageable, EpisodeMappingDto::class.java),
totalDuration = totalDuration,
totalUnseenDuration = totalUnseenDuration,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package fr.shikkanime.converters.member
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.TokenDto
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.Member
import fr.shikkanime.utils.Constant
import java.util.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package fr.shikkanime.dtos
package fr.shikkanime.dtos.member

import java.util.*

Expand All @@ -12,5 +12,6 @@ data class MemberDto(
val followedAnimes: List<UUID>,
val followedEpisodes: List<UUID>,
val totalDuration: Long,
val totalUnseenDuration: Long,
val hasProfilePicture: Boolean = false,
)
14 changes: 14 additions & 0 deletions src/main/kotlin/fr/shikkanime/dtos/member/RefreshMemberDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.shikkanime.dtos.member

import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.MissedAnimeDto
import fr.shikkanime.dtos.PageableDto
import fr.shikkanime.dtos.mappings.EpisodeMappingDto

data class RefreshMemberDto(
val missedAnimes: PageableDto<MissedAnimeDto>,
val followedAnimes: PageableDto<AnimeDto>,
val followedEpisodes: PageableDto<EpisodeMappingDto>,
val totalDuration: Long,
val totalUnseenDuration: Long,
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package fr.shikkanime.dtos
package fr.shikkanime.dtos.member

import io.ktor.server.auth.*

Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/fr/shikkanime/modules/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fr.shikkanime.modules

import fr.shikkanime.dtos.*
import fr.shikkanime.dtos.enums.Status
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.enums.ConfigPropertyKey
import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.entities.enums.LangType
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/shikkanime/modules/Security.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import fr.shikkanime.dtos.MessageDto
import fr.shikkanime.dtos.TokenDto
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.enums.Role
import fr.shikkanime.services.caches.MemberCacheService
import fr.shikkanime.utils.Constant
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fr.shikkanime.repositories

import fr.shikkanime.entities.*
import fr.shikkanime.entities.enums.EpisodeType
import jakarta.persistence.criteria.JoinType
import java.util.*

class MemberFollowEpisodeRepository : AbstractRepository<MemberFollowEpisode>() {
Expand Down Expand Up @@ -112,20 +114,44 @@ class MemberFollowEpisodeRepository : AbstractRepository<MemberFollowEpisode>()
}
}

fun getTotalDuration(member: Member): Long {
fun getSeenAndUnseenDuration(member: Member): Pair<Long, Long> {
return database.entityManager.use {
val cb = it.criteriaBuilder
val query = cb.createQuery(Long::class.java)
val root = query.from(getEntityClass())
query.select(cb.sum(root[MemberFollowEpisode_.episode][EpisodeMapping_.duration]))
val query = cb.createTupleQuery()
val root = query.from(MemberFollowAnime::class.java)
val anime = root.join(MemberFollowAnime_.anime)
val episodeMapping = anime.join(Anime_.mappings, JoinType.LEFT)
val memberFollowEpisode = episodeMapping.join(EpisodeMapping_.memberFollowEpisodes, JoinType.LEFT)
memberFollowEpisode.on(cb.equal(memberFollowEpisode[MemberFollowEpisode_.member], member))

val seenDuration = cb.sum(
cb.selectCase<Number?>()
.`when`(cb.isNotNull(memberFollowEpisode[MemberFollowEpisode_.episode]), episodeMapping[EpisodeMapping_.duration])
.otherwise(0)
)

query.where(
cb.equal(root[MemberFollowEpisode_.member], member)
val unseenDuration = cb.sum(
cb.selectCase<Number?>()
.`when`(
cb.and(
cb.isNull(memberFollowEpisode[MemberFollowEpisode_.episode]),
cb.notEqual(episodeMapping[EpisodeMapping_.episodeType], EpisodeType.SUMMARY)
),
episodeMapping[EpisodeMapping_.duration]
)
.otherwise(0)
)

it.createQuery(query)
.resultList
.firstOrNull() ?: 0L
query.multiselect(
cb.coalesce(seenDuration, 0L),
cb.coalesce(unseenDuration, 0L)
)

query.where(cb.equal(root[MemberFollowAnime_.member], member))

createReadOnlyQuery(it, query)
.singleResult
.let { pair -> pair[0] as Long to pair[1] as Long }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class MemberFollowEpisodeService : AbstractService<MemberFollowEpisode, MemberFo
fun findAllByEpisode(episodeMapping: EpisodeMapping) =
memberFollowEpisodeRepository.findAllByEpisode(episodeMapping)

fun getTotalDuration(member: Member) = memberFollowEpisodeRepository.getTotalDuration(member)
fun getSeenAndUnseenDuration(member: Member) = memberFollowEpisodeRepository.getSeenAndUnseenDuration(member)

fun followAll(memberUuid: UUID, anime: GenericDto): Response {
val member = memberService.find(memberUuid) ?: return Response.notFound()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package fr.shikkanime.services.caches

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.MemberDto
import fr.shikkanime.dtos.member.MemberDto
import fr.shikkanime.dtos.member.RefreshMemberDto
import fr.shikkanime.entities.*
import fr.shikkanime.services.MemberService
import fr.shikkanime.utils.MapCache
Expand Down Expand Up @@ -32,7 +33,21 @@ class MemberCacheService : AbstractCacheService {
?.let { member -> AbstractConverter.convert(member, MemberDto::class.java) }
}

private val refreshMemberCache = MapCache<UUID, RefreshMemberDto?>(
classes = listOf(
Member::class.java,
Anime::class.java,
MemberFollowAnime::class.java,
EpisodeMapping::class.java,
MemberFollowEpisode::class.java,
)
) {
memberService.find(it)?.let { member -> AbstractConverter.convert(member, RefreshMemberDto::class.java) }
}

fun find(uuid: UUID) = cache[uuid]

fun findByIdentifier(identifier: String) = findByIdentifierCache[identifier]

fun getRefreshMember(uuid: UUID) = refreshMemberCache[uuid]
}
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/shikkanime/utils/routes/Response.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package fr.shikkanime.utils.routes

import fr.shikkanime.dtos.TokenDto
import fr.shikkanime.dtos.member.TokenDto
import fr.shikkanime.entities.enums.Link
import io.ktor.http.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.shikkanime.controllers.api

import com.google.inject.Inject
import fr.shikkanime.dtos.MemberDto
import fr.shikkanime.dtos.member.MemberDto
import fr.shikkanime.entities.*
import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.entities.enums.EpisodeType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.shikkanime.controllers.api

import fr.shikkanime.dtos.GenericDto
import fr.shikkanime.dtos.MemberDto
import fr.shikkanime.dtos.member.MemberDto
import fr.shikkanime.entities.Member
import fr.shikkanime.entities.MemberAction
import fr.shikkanime.entities.enums.Action
Expand Down

0 comments on commit ea42922

Please sign in to comment.