diff --git a/src/main/kotlin/fr/shikkanime/controllers/site/SEOController.kt b/src/main/kotlin/fr/shikkanime/controllers/site/SEOController.kt index 0e71df03..0f2d990a 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/site/SEOController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/site/SEOController.kt @@ -1,23 +1,25 @@ package fr.shikkanime.controllers.site import com.google.inject.Inject +import fr.shikkanime.dtos.URLDto import fr.shikkanime.entities.SortParameter import fr.shikkanime.entities.enums.CountryCode +import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.Link -import fr.shikkanime.services.caches.AnimeCacheService import fr.shikkanime.services.caches.EpisodeMappingCacheService import fr.shikkanime.services.caches.SimulcastCacheService +import fr.shikkanime.utils.Constant import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.Path import fr.shikkanime.utils.routes.Response import fr.shikkanime.utils.routes.method.Get +import fr.shikkanime.utils.withUTCString import io.ktor.http.* import java.time.ZonedDateTime @Controller("/") class SEOController { - @Inject - private lateinit var animeCacheService: AnimeCacheService + private fun ZonedDateTime.formatDateTime() = this.withUTCString().replace("Z", "+00:00") @Inject private lateinit var episodeMappingCacheService: EpisodeMappingCacheService @@ -38,36 +40,53 @@ class SEOController { @Path("sitemap.xml") @Get private fun sitemap(): Response { - val simulcasts = simulcastCacheService.findAll()!!.toMutableList() - val animes = animeCacheService.findAll()!! - .sortedBy { it.shortName.lowercase() } - - simulcasts.forEach { simulcast -> - simulcast.lastReleaseDateTime = - animes.filter { anime -> anime.simulcasts!!.size == 1 && anime.simulcasts.any { it.uuid == simulcast.uuid } } - .maxByOrNull { d -> ZonedDateTime.parse(d.releaseDateTime) }?.releaseDateTime - } + val globalLastModification = "2024-03-20T17:00:00+00:00" - simulcasts.removeIf { simulcast -> simulcast.lastReleaseDateTime == null } - - val episodeMapping = episodeMappingCacheService.findAllBy( + val lastReleaseDateTime = episodeMappingCacheService.findAllBy( CountryCode.FR, null, null, listOf(SortParameter("lastReleaseDateTime", SortParameter.Order.DESC)), 1, 1 - )!!.data.firstOrNull() + )?.data?.firstOrNull()?.lastReleaseDateTime?.replace("Z", "+00:00") ?: globalLastModification + + val urls = mutableSetOf( + URLDto(Constant.baseUrl, lastReleaseDateTime), + URLDto("${Constant.baseUrl}/calendar", lastReleaseDateTime), + URLDto("${Constant.baseUrl}/search", globalLastModification) + ) + + simulcastCacheService.findAllModified()?.mapTo(urls) { + URLDto("${Constant.baseUrl}/catalog/${it.slug}", it.lastReleaseDateTime!!) + } + + episodeMappingCacheService.findAllSeo()?.groupBy { it[0] as String }?.forEach { (animeSlug, episodes) -> + val seasonMap = episodes.groupBy { it[1] as Int } + val firstSeasonDateTime = seasonMap.values.flatten().maxOf { it[4] as ZonedDateTime } + + urls.add(URLDto("${Constant.baseUrl}/animes/$animeSlug", firstSeasonDateTime.formatDateTime())) + + seasonMap.forEach { (season, seasonEpisodes) -> + val lastSeasonDateTime = seasonEpisodes.maxOf { it[4] as ZonedDateTime } + urls.add(URLDto("${Constant.baseUrl}/animes/$animeSlug/season-$season", lastSeasonDateTime.formatDateTime())) + + seasonEpisodes.forEach { + val episodeType = it[2] as EpisodeType + val number = it[3] as Int + val episodeDateTime = it[4] as ZonedDateTime + urls.add(URLDto("${Constant.baseUrl}/animes/$animeSlug/season-$season/${episodeType.slug}-$number", episodeDateTime.formatDateTime())) + } + } + } + + Link.entries.filter { !it.href.startsWith("/admin") && it.footer } + .mapTo(urls) { URLDto("${Constant.baseUrl}${it.href}", globalLastModification) } return Response.template( "/site/seo/sitemap.ftl", null, - mutableMapOf( - "episodeMapping" to episodeMapping, - "simulcasts" to simulcasts, - "animes" to animes, - "seoLinks" to Link.entries.filter { !it.href.startsWith("/admin") && it.footer }.toList() - ), + mutableMapOf("urls" to urls), contentType = ContentType.Text.Xml ) } diff --git a/src/main/kotlin/fr/shikkanime/converters/episode_mapping/EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter.kt b/src/main/kotlin/fr/shikkanime/converters/episode_mapping/EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter.kt index 2a1f8956..884cea2b 100644 --- a/src/main/kotlin/fr/shikkanime/converters/episode_mapping/EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter.kt +++ b/src/main/kotlin/fr/shikkanime/converters/episode_mapping/EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter.kt @@ -1,13 +1,23 @@ package fr.shikkanime.converters.episode_mapping +import com.google.inject.Inject import fr.shikkanime.converters.AbstractConverter +import fr.shikkanime.dtos.PlatformDto import fr.shikkanime.dtos.mappings.EpisodeMappingWithoutAnimeDto +import fr.shikkanime.dtos.variants.EpisodeVariantWithoutMappingDto import fr.shikkanime.entities.EpisodeMapping +import fr.shikkanime.entities.enums.LangType +import fr.shikkanime.services.EpisodeVariantService import fr.shikkanime.utils.withUTCString class EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter : AbstractConverter() { + @Inject + private lateinit var episodeVariantService: EpisodeVariantService + override fun convert(from: EpisodeMapping): EpisodeMappingWithoutAnimeDto { + val variants = episodeVariantService.findAllByMapping(from).sortedBy { it.releaseDateTime } + return EpisodeMappingWithoutAnimeDto( uuid = from.uuid!!, releaseDateTime = from.releaseDateTime.withUTCString(), @@ -20,6 +30,14 @@ class EpisodeMappingToEpisodeMappingWithoutAnimeDtoConverter : title = from.title, description = from.description, image = from.image!!, + variants = convert(variants, EpisodeVariantWithoutMappingDto::class.java), + platforms = convert( + variants.mapNotNull { it.platform }.sortedBy { it.name }.toSet(), + PlatformDto::class.java + )?.toList(), + langTypes = variants.map { LangType.fromAudioLocale(from.anime!!.countryCode!!, it.audioLocale!!) } + .distinct() + .sorted(), status = from.status ) } diff --git a/src/main/kotlin/fr/shikkanime/dtos/URLDto.kt b/src/main/kotlin/fr/shikkanime/dtos/URLDto.kt new file mode 100644 index 00000000..954796d5 --- /dev/null +++ b/src/main/kotlin/fr/shikkanime/dtos/URLDto.kt @@ -0,0 +1,6 @@ +package fr.shikkanime.dtos + +data class URLDto( + val absoluteURL: String, + val lastModification: String, +) diff --git a/src/main/kotlin/fr/shikkanime/dtos/WeeklyAnimeDto.kt b/src/main/kotlin/fr/shikkanime/dtos/WeeklyAnimeDto.kt index 80d99cdb..82dd7899 100644 --- a/src/main/kotlin/fr/shikkanime/dtos/WeeklyAnimeDto.kt +++ b/src/main/kotlin/fr/shikkanime/dtos/WeeklyAnimeDto.kt @@ -1,8 +1,8 @@ package fr.shikkanime.dtos +import fr.shikkanime.dtos.mappings.EpisodeMappingWithoutAnimeDto import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.LangType -import java.util.* data class WeeklyAnimeDto( val anime: AnimeDto, @@ -10,11 +10,10 @@ data class WeeklyAnimeDto( val releaseDateTime: String, val slug: String, val langType: LangType, - val isReleased: Boolean, - val isMultipleReleased: Boolean, - val mappings: List, + val episodeType: EpisodeType? = null, val minNumber: Int? = null, val maxNumber: Int? = null, val number: Int? = null, + val mappings: List? = null ) diff --git a/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingDto.kt b/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingDto.kt index 53b1be27..c30ab0cc 100644 --- a/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingDto.kt +++ b/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingDto.kt @@ -21,7 +21,7 @@ data class EpisodeMappingDto( val title: String?, val description: String?, val image: String, - val variants: List?, + val variants: List? = null, val platforms: List? = null, val langTypes: List? = null, val status: Status, diff --git a/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingWithoutAnimeDto.kt b/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingWithoutAnimeDto.kt index cfa47949..5181369a 100644 --- a/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingWithoutAnimeDto.kt +++ b/src/main/kotlin/fr/shikkanime/dtos/mappings/EpisodeMappingWithoutAnimeDto.kt @@ -1,7 +1,10 @@ package fr.shikkanime.dtos.mappings +import fr.shikkanime.dtos.PlatformDto import fr.shikkanime.dtos.enums.Status +import fr.shikkanime.dtos.variants.EpisodeVariantWithoutMappingDto import fr.shikkanime.entities.enums.EpisodeType +import fr.shikkanime.entities.enums.LangType import java.util.* data class EpisodeMappingWithoutAnimeDto( @@ -16,5 +19,8 @@ data class EpisodeMappingWithoutAnimeDto( val title: String?, val description: String?, val image: String, + val variants: List? = null, + val platforms: List? = null, + val langTypes: List? = null, val status: Status, ) diff --git a/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt b/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt index d4c1f773..ce8052ea 100644 --- a/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt +++ b/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt @@ -152,14 +152,12 @@ class CrunchyrollPlatform : alreadyFetched: List ): List { val list = mutableListOf() - - val lastWeek = zonedDateTime.minusWeeks(1) - val lastWeekStartOfTheDay = lastWeek.withHour(0).withMinute(0).withSecond(0).withNano(0) + val lastWeek = zonedDateTime.minusWeeks(1).toLocalDate() episodeVariantService.findAllIdentifierByDateRangeWithoutNextEpisode( countryCode, - lastWeekStartOfTheDay, - lastWeek.plusSeconds(1), + lastWeek.atStartOfDay(Constant.utcZoneId), + lastWeek.atEndOfTheDay(Constant.utcZoneId), getPlatform() ).forEach { identifier -> val crunchyrollId = getCrunchyrollId(identifier) ?: run { diff --git a/src/main/kotlin/fr/shikkanime/repositories/EpisodeMappingRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/EpisodeMappingRepository.kt index f2a1e30c..e4fe6cd5 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/EpisodeMappingRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/EpisodeMappingRepository.kt @@ -145,6 +145,27 @@ class EpisodeMappingRepository : AbstractRepository() { } } + fun findAllSeo(): List { + return database.entityManager.use { + val cb = it.criteriaBuilder + val query = cb.createTupleQuery() + val root = query.from(getEntityClass()) + + query.multiselect( + root[EpisodeMapping_.anime][Anime_.slug], + root[EpisodeMapping_.season], + root[EpisodeMapping_.episodeType], + root[EpisodeMapping_.number], + root[EpisodeMapping_.lastReleaseDateTime], + ) + + query.orderBy(cb.asc(root[EpisodeMapping_.releaseDateTime])) + + createReadOnlyQuery(it, query) + .resultList + } + } + fun findByAnimeSeasonEpisodeTypeNumber( animeUuid: UUID, season: Int, diff --git a/src/main/kotlin/fr/shikkanime/repositories/SimulcastRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/SimulcastRepository.kt index 59a5f048..34efd4b8 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/SimulcastRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/SimulcastRepository.kt @@ -1,7 +1,10 @@ package fr.shikkanime.repositories +import fr.shikkanime.entities.Anime +import fr.shikkanime.entities.Anime_ import fr.shikkanime.entities.Simulcast import fr.shikkanime.entities.Simulcast_ +import jakarta.persistence.Tuple class SimulcastRepository : AbstractRepository() { override fun getEntityClass() = Simulcast::class.java @@ -20,6 +23,28 @@ class SimulcastRepository : AbstractRepository() { } } + fun findAllModified(): List { + return database.entityManager.use { + val cb = it.criteriaBuilder + val query = cb.createTupleQuery() + + val root = query.from(Anime::class.java) + val simulcastJoin = root.join(Anime_.simulcasts) + + query.multiselect( + simulcastJoin, + cb.greatest(root[Anime_.releaseDateTime]) + ) + + query.groupBy(simulcastJoin) + query.orderBy(cb.desc(cb.greatest(root[Anime_.releaseDateTime]))) + + createReadOnlyQuery(it, query) + .resultList + } + } + + fun findBySeasonAndYear(season: String, year: Int): Simulcast? { return database.entityManager.use { val cb = it.criteriaBuilder diff --git a/src/main/kotlin/fr/shikkanime/services/AnimeService.kt b/src/main/kotlin/fr/shikkanime/services/AnimeService.kt index 8519de93..e2798583 100644 --- a/src/main/kotlin/fr/shikkanime/services/AnimeService.kt +++ b/src/main/kotlin/fr/shikkanime/services/AnimeService.kt @@ -7,16 +7,14 @@ import fr.shikkanime.dtos.PlatformDto import fr.shikkanime.dtos.WeeklyAnimeDto import fr.shikkanime.dtos.WeeklyAnimesDto import fr.shikkanime.dtos.enums.Status +import fr.shikkanime.dtos.mappings.EpisodeMappingWithoutAnimeDto import fr.shikkanime.entities.* import fr.shikkanime.entities.enums.CountryCode import fr.shikkanime.entities.enums.LangType import fr.shikkanime.entities.enums.Platform import fr.shikkanime.repositories.AnimeRepository -import fr.shikkanime.utils.MapCache -import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.* import fr.shikkanime.utils.StringUtils.capitalizeWords -import fr.shikkanime.utils.withUTCString -import java.time.DayOfWeek import java.time.LocalDate import java.time.ZoneId import java.time.ZonedDateTime @@ -88,48 +86,55 @@ class AnimeService : AbstractService() { fun getWeeklyAnimes(countryCode: CountryCode, member: Member?, startOfWeekDay: LocalDate): List { val zoneId = ZoneId.of(countryCode.timezone) val startOfPreviousWeek = startOfWeekDay.minusWeeks(1).atStartOfDay(zoneId) - val endOfWeek = startOfWeekDay.with(DayOfWeek.SUNDAY).atTime(23, 59, 59).atZone(zoneId) + val endOfWeek = startOfWeekDay.atEndOfWeek().atEndOfTheDay(zoneId) val tuples = episodeVariantService.findAllAnimeEpisodeMappingReleaseDateTimePlatformAudioLocaleByDateRange( countryCode, member, startOfPreviousWeek, endOfWeek ) - return startOfWeekDay.datesUntil(startOfWeekDay.plusDays(7)).toList().map { date -> + val dateFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.forLanguageTag(countryCode.locale)) + val currentWeek = startOfWeekDay[ChronoField.ALIGNED_WEEK_OF_YEAR] + + return (0..6).map { dayOffset -> + val date = startOfWeekDay.plusDays(dayOffset.toLong()) val zonedDate = date.atStartOfDay(zoneId) val tuplesDay = tuples.filter { (it[2] as ZonedDateTime).withZoneSameInstant(zoneId).dayOfWeek == zonedDate.dayOfWeek } WeeklyAnimesDto( - date.format(DateTimeFormatter.ofPattern("EEEE", Locale.forLanguageTag(countryCode.locale))).capitalizeWords(), + date.format(dateFormatter).capitalizeWords(), tuplesDay.groupBy { triple -> val anime = triple[0] as Anime - Triple(anime, (triple[1] as EpisodeMapping).episodeType!!, LangType.fromAudioLocale(anime.countryCode!!, triple[4] as String)) - }.map { (triple, values) -> - val anime = triple.first + anime to LangType.fromAudioLocale(anime.countryCode!!, triple[4] as String) + }.flatMap { (pair, values) -> + val (anime, langType) = pair val releaseDateTime = values.maxOf { it[2] as ZonedDateTime } val mappings = values.filter { - (it[2] as ZonedDateTime).withZoneSameInstant(zoneId)[ChronoField.ALIGNED_WEEK_OF_YEAR] == startOfWeekDay[ChronoField.ALIGNED_WEEK_OF_YEAR] + (it[2] as ZonedDateTime).withZoneSameInstant(zoneId)[ChronoField.ALIGNED_WEEK_OF_YEAR] == currentWeek }.map { it[1] as EpisodeMapping } .distinctBy { it.uuid } .sortedWith(compareBy({ it.releaseDateTime }, { it.season }, { it.episodeType }, { it.number })) - WeeklyAnimeDto( - AbstractConverter.convert(anime, AnimeDto::class.java), - AbstractConverter.convert(values.map { it[3] as Platform }.distinct(), PlatformDto::class.java)!!, - releaseDateTime.withUTCString(), - "/animes/${anime.slug}${ - mappings.firstOrNull() - ?.let { "/season-${it.season}" + ("/${it.episodeType!!.slug}-${it.number}".takeIf { mappings.size <= 1 } ?: "") } ?: "" - }", - triple.third, - mappings.isNotEmpty(), - mappings.size > 1, - mappings.map { it.uuid!! }, - mappings.firstOrNull()?.episodeType, - mappings.minOfOrNull { it.number!! }, - mappings.maxOfOrNull { it.number!! }, - mappings.firstOrNull()?.number - ) + mappings.groupBy { it.episodeType }.ifEmpty { mapOf(null to mappings) }.map { (episodeType, episodeMappings) -> + WeeklyAnimeDto( + AbstractConverter.convert(anime, AnimeDto::class.java), + AbstractConverter.convert(values.mapNotNull { it[3] as? Platform }.distinct(), PlatformDto::class.java)!!, + releaseDateTime.withUTCString(), + buildString { + append("/animes/${anime.slug}") + episodeMappings.firstOrNull()?.let { + append("/season-${it.season}") + if (mappings.size <= 1) append("/${it.episodeType!!.slug}-${it.number}") + } + }, + langType, + episodeType, + episodeMappings.minOfOrNull { it.number!! }, + episodeMappings.maxOfOrNull { it.number!! }, + episodeMappings.firstOrNull()?.number, + AbstractConverter.convert(episodeMappings.takeIf { it.isNotEmpty() }, EpisodeMappingWithoutAnimeDto::class.java) + ) + } }.sortedWith( compareBy( { ZonedDateTime.parse(it.releaseDateTime).withZoneSameInstant(zoneId).toLocalTime() }, diff --git a/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt b/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt index 90d02560..016445a8 100644 --- a/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt +++ b/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt @@ -50,6 +50,8 @@ class EpisodeMappingService : AbstractService() { override fun findAll() = super.findAll().sortBySeasonAndYear() + fun findAllModified() = simulcastRepository.findAllModified() + fun findBySeasonAndYear(season: String, year: Int) = simulcastRepository.findBySeasonAndYear(season, year) override fun save(entity: Simulcast): Simulcast { diff --git a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt index 2a527605..50001705 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt @@ -10,12 +10,10 @@ import fr.shikkanime.dtos.AnimeDto import fr.shikkanime.dtos.PageableDto import fr.shikkanime.dtos.WeeklyAnimesDto import fr.shikkanime.dtos.enums.Status -import fr.shikkanime.dtos.mappings.EpisodeMappingWithoutAnimeDto import fr.shikkanime.entities.* import fr.shikkanime.entities.enums.CountryCode import fr.shikkanime.entities.enums.LangType import fr.shikkanime.services.AnimeService -import fr.shikkanime.services.EpisodeMappingService import fr.shikkanime.services.MemberService import fr.shikkanime.services.SimulcastService import fr.shikkanime.utils.MapCache @@ -32,9 +30,6 @@ class AnimeCacheService : AbstractCacheService { @Inject private lateinit var memberService: MemberService - @Inject - private lateinit var episodeMappingService: EpisodeMappingService - private val findAllByCache = MapCache>(classes = listOf(Anime::class.java)) { PageableDto.fromPageable( @@ -69,26 +64,6 @@ class AnimeCacheService : AbstractCacheService { .let { anime -> AbstractConverter.convert(anime, AnimeDto::class.java) } } - private val findAllCache = MapCache>( - classes = listOf( - Anime::class.java, - EpisodeMapping::class.java, - EpisodeVariant::class.java - ) - ) { - val list = animeService.findAllLoaded() - val dtos = list.associateWith { AbstractConverter.convert(it, AnimeDto::class.java) } - - dtos.forEach { (anime, dto) -> - dto.episodes = AbstractConverter.convert( - episodeMappingService.findAllByAnime(anime), - EpisodeMappingWithoutAnimeDto::class.java - ) - } - - dtos.values.toList() - } - private val weeklyMemberCache = MapCache>( classes = listOf( @@ -121,6 +96,4 @@ class AnimeCacheService : AbstractCacheService { fun getWeeklyAnimes(countryCode: CountryCode, memberUuid: UUID?, startOfWeekDay: LocalDate) = weeklyMemberCache[CountryCodeLocalDateKeyCache(memberUuid, countryCode, startOfWeekDay)] - - fun findAll() = findAllCache["all"] } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt index daefa094..2738a88e 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt @@ -15,6 +15,7 @@ import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.services.AnimeService import fr.shikkanime.services.EpisodeMappingService import fr.shikkanime.utils.MapCache +import jakarta.persistence.Tuple import java.util.* class EpisodeMappingCacheService : AbstractCacheService { @@ -45,6 +46,10 @@ class EpisodeMappingCacheService : AbstractCacheService { ) } + private val findAllSeo = MapCache>(classes = listOf(EpisodeMapping::class.java)) { + episodeMappingService.findAllSeo() + } + private val findByAnimeSeasonEpisodeTypeNumberCache = MapCache>( classes = listOf( @@ -83,6 +88,8 @@ class EpisodeMappingCacheService : AbstractCacheService { status: Status? = null ) = findAllByCache[CountryCodeUUIDSeasonSortPaginationKeyCache(countryCode, anime, season, sort, page, limit, status)] + fun findAllSeo() = findAllSeo["seo"] + fun findByAnimeSeasonEpisodeTypeNumber( animeUuid: UUID, season: Int, diff --git a/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt index 33a69493..799a46d7 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt @@ -6,6 +6,8 @@ import fr.shikkanime.dtos.SimulcastDto import fr.shikkanime.entities.Simulcast import fr.shikkanime.services.SimulcastService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.withUTCString +import java.time.ZonedDateTime class SimulcastCacheService : AbstractCacheService { @Inject @@ -15,8 +17,18 @@ class SimulcastCacheService : AbstractCacheService { AbstractConverter.convert(simulcastService.findAll(), SimulcastDto::class.java)!! } + private val modifiedCache = MapCache>(classes = listOf(Simulcast::class.java)) { + val list = simulcastService.findAllModified().map { it[0] as Simulcast to it[1] as ZonedDateTime } + + AbstractConverter.convert(list.map { it.first }, SimulcastDto::class.java)!!.onEach { simulcastDto -> + simulcastDto.lastReleaseDateTime = list.firstOrNull { it.first.uuid == simulcastDto.uuid }!!.second.withUTCString() + } + } + fun findAll() = cache["all"] + fun findAllModified() = modifiedCache["all"] + val currentSimulcast: SimulcastDto? get() = findAll()?.firstOrNull() } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/utils/Extensions.kt b/src/main/kotlin/fr/shikkanime/utils/Extensions.kt index 7e2025ab..b455719a 100644 --- a/src/main/kotlin/fr/shikkanime/utils/Extensions.kt +++ b/src/main/kotlin/fr/shikkanime/utils/Extensions.kt @@ -25,6 +25,14 @@ fun LocalDate.atStartOfWeek(): LocalDate { return this.with(DayOfWeek.MONDAY) } +fun LocalDate.atEndOfWeek(): LocalDate { + return this.with(DayOfWeek.SUNDAY) +} + +fun LocalDate.atEndOfTheDay(zoneId: ZoneId): ZonedDateTime { + return this.atTime(LocalTime.MAX).atZone(zoneId) +} + fun BufferedImage.resize(width: Int, height: Int): BufferedImage { return ResampleOp(width, height).filter(this, null) } diff --git a/src/main/resources/templates/_freemarker_implicit.ftl b/src/main/resources/templates/_freemarker_implicit.ftl index beb36c82..eca80c5f 100644 --- a/src/main/resources/templates/_freemarker_implicit.ftl +++ b/src/main/resources/templates/_freemarker_implicit.ftl @@ -17,7 +17,6 @@ [#-- @ftlvariable name="seoDescription" type="java.lang.String" --] [#-- @ftlvariable name="currentSimulcast" type="fr.shikkanime.dtos.SimulcastDto" --] [#-- @ftlvariable name="footerLinks" type="kotlin.collections.AbstractList" --] -[#-- @ftlvariable name="seoLinks" type="kotlin.collections.AbstractList" --] [#-- @ftlvariable name="su" type="fr.shikkanime.utils.StringUtils" --] [#-- @ftlvariable name="weeklyAnimes" type="kotlin.collections.AbstractList" --] [#-- @ftlvariable name="query" type="java.lang.String" --] @@ -31,8 +30,6 @@ [#-- @ftlvariable name="episodeMappings" type="kotlin.collections.AbstractList" --] [#-- @ftlvariable name="episodeMapping" type="fr.shikkanime.dtos.mappings.EpisodeMappingDto" --] -[#-- @ftlvariable name="episodeVariants" type="kotlin.collections.AbstractList" --] -[#-- @ftlvariable name="episodeVariant" type="fr.shikkanime.dtos.variants.EpisodeVariantDto" --] [#-- @ftlvariable name="previousEpisode" type="fr.shikkanime.dtos.mappings.EpisodeMappingDto" --] [#-- @ftlvariable name="nextEpisode" type="fr.shikkanime.dtos.mappings.EpisodeMappingDto" --] @@ -40,3 +37,4 @@ [#-- @ftlvariable name="showMore" type="java.lang.Boolean" --] [#-- @ftlvariable name="showLess" type="java.lang.Boolean" --] [#-- @ftlvariable name="page" type="java.lang.Integer" --] +[#-- @ftlvariable name="urls" type="kotlin.collections.AbstractList" --] diff --git a/src/main/resources/templates/site/calendar.ftl b/src/main/resources/templates/site/calendar.ftl index 3fdca978..6f3201c0 100644 --- a/src/main/resources/templates/site/calendar.ftl +++ b/src/main/resources/templates/site/calendar.ftl @@ -55,14 +55,17 @@ <#list weeklyAnimes as dailyAnimes> <#list dailyAnimes.releases as release> + <#assign isReleased = (release.mappings?? && release.mappings?size > 0)> + <#assign isMultipleReleased = isReleased && (release.mappings?size > 1)> + <#assign imageUrl = "${apiUrl}/v1/attachments?uuid=${release.anime.uuid}&type=banner"> - <#if release.isReleased()> - <#assign imageUrl = "${apiUrl}/v1/attachments?uuid=${release.mappings?first}&type=image"> + <#if isReleased> + <#assign imageUrl = "${apiUrl}/v1/attachments?uuid=${release.mappings?first.uuid}&type=image"> -
- <#if release.isMultipleReleased()> +
+ <#if isMultipleReleased>
@@ -99,7 +102,7 @@ ${release.anime.shortName} <#if release.minNumber?? || release.maxNumber?? || release.number??>

- ${getPrefixEpisode(release.episodeType)} <#if release.isMultipleReleased()>${release.minNumber?c} - ${release.maxNumber?c}<#else>${release.number?c} + ${getPrefixEpisode(release.episodeType)} <#if isMultipleReleased>${release.minNumber?c} - ${release.maxNumber?c}<#else>${release.number?c}

<@langTypeComponent.display langType=release.langType />

@@ -114,7 +117,7 @@
- <#if release.isReleased()> + <#if isReleased>
- <#if episodeMapping??> + <#list urls as url> - ${baseUrl}/ - ${episodeMapping.lastReleaseDateTime?replace("Z", "+00:00")} - - - ${baseUrl}/calendar - ${episodeMapping.lastReleaseDateTime?replace("Z", "+00:00")} - - - - ${baseUrl}/search - 2024-03-20T17:00:00+00:00 - - <#list simulcasts as simulcast> - - ${baseUrl}/catalog/${simulcast.slug} - ${simulcast.lastReleaseDateTime?replace("Z", "+00:00")} - - - <#list animes as anime> - - ${baseUrl}/animes/${anime.slug} - ${anime.seasons?first.lastReleaseDateTime?replace("Z", "+00:00")} - - <#list anime.seasons as season> - - ${baseUrl}/animes/${anime.slug}/season-${season.number} - ${season.lastReleaseDateTime?replace("Z", "+00:00")} - - - <#list anime.episodes as episode> - - ${baseUrl}/animes/${anime.slug}/season-${episode.season?c}/${episode.episodeType.slug}-${episode.number?c} - ${episode.lastReleaseDateTime?replace("Z", "+00:00")} - - - - <#list seoLinks as link> - - ${baseUrl}${link.href} - 2024-02-27T17:00:00+00:00 + ${url.absoluteURL} + ${url.lastModification} \ No newline at end of file