Skip to content

Commit

Permalink
Add calender on website
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Mar 22, 2024
1 parent 633d80b commit b65a817
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 11 deletions.
14 changes: 14 additions & 0 deletions src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import fr.shikkanime.utils.routes.method.Get
import fr.shikkanime.utils.routes.param.PathParam
import fr.shikkanime.utils.routes.param.QueryParam
import io.ktor.http.*
import java.time.ZonedDateTime

@Controller("/")
class SiteController {
Expand Down Expand Up @@ -152,6 +153,19 @@ class SiteController {
)
}

@Path("calendar")
@Get
private fun calendar(): Response {
val now = ZonedDateTime.now().toLocalDate()

return Response.template(
Link.CALENDAR,
mutableMapOf(
"weeklyEpisodes" to episodeCacheService.getWeeklyEpisodes(now.minusDays(now.dayOfWeek.value.toLong() - 1)),
)
)
}

@Path("presentation")
@Get
private fun presentation(): Response {
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/fr/shikkanime/dtos/WeeklyEpisodesDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fr.shikkanime.dtos

data class WeeklyEpisodesDto(
val dayOfWeek: String,
val episodes: List<EpisodeDto>
)
1 change: 1 addition & 0 deletions src/main/kotlin/fr/shikkanime/entities/enums/Link.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class Link(
// Site
HOME("/", "/site/home.ftl", "", "Accueil", "${Constant.NAME} : Ne manquez plus jamais un épisode d'animé !"),
CATALOG("/catalog/{currentSimulcast}", "/site/catalog.ftl", "", "Catalogue"),
CALENDAR("/calendar", "/site/calendar.ftl", "", "Calendrier"),
SEARCH("/search", "/site/search.ftl", "", "Recherche"),
PRESENTATION("/presentation", "/site/presentation.ftl", "", "Présentation", footer = true),
;
Expand Down
25 changes: 25 additions & 0 deletions src/main/kotlin/fr/shikkanime/repositories/EpisodeRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,29 @@ class EpisodeRepository : AbstractRepository<Episode>() {
.initialize()
}
}

fun findAllByDateRange(
countryCode: CountryCode,
start: ZonedDateTime,
end: ZonedDateTime,
blacklisted: List<UUID> = emptyList(),
): List<Episode> {
return inTransaction { entityManager ->
createReadOnlyQuery(
entityManager,
"""
FROM Episode e
WHERE e.anime.countryCode = :countryCode AND e.releaseDateTime BETWEEN :start AND :end AND e.anime.uuid NOT IN :blacklisted
ORDER BY e.releaseDateTime ASC
""".trimIndent(),
Tuple::class.java
)
.setParameter("countryCode", countryCode)
.setParameter("start", start)
.setParameter("end", end)
.setParameter("blacklisted", blacklisted)
.resultList
.map { it.get(0, Episode::class.java).initialize() }
}
}
}
33 changes: 32 additions & 1 deletion src/main/kotlin/fr/shikkanime/services/EpisodeService.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fr.shikkanime.services

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.EpisodeDto
import fr.shikkanime.dtos.WeeklyEpisodesDto
import fr.shikkanime.entities.Anime
import fr.shikkanime.entities.Episode
import fr.shikkanime.entities.Simulcast
Expand All @@ -10,8 +13,11 @@ import fr.shikkanime.repositories.EpisodeRepository
import fr.shikkanime.services.caches.ConfigCacheService
import fr.shikkanime.utils.Constant
import fr.shikkanime.utils.MapCache
import fr.shikkanime.utils.StringUtils.capitalizeWords
import io.ktor.http.*
import java.time.LocalDate
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.*

Expand Down Expand Up @@ -51,6 +57,31 @@ class EpisodeService : AbstractService<Episode, EpisodeRepository>() {
) =
episodeRepository.findAllByPlatformDeprecatedEpisodes(platform, lastUpdateDateTime, notLike)

fun getWeeklyEpisodes(startOfWeekDay: LocalDate): List<WeeklyEpisodesDto> {
return startOfWeekDay.datesUntil(startOfWeekDay.plusDays(7)).map {
val start = ZonedDateTime.parse("${it}T00:00:00Z")
val end = ZonedDateTime.parse("${it}T23:59:59Z")
val dateTitle = it.format(DateTimeFormatter.ofPattern("EEEE")).capitalizeWords()
val list = episodeRepository.findAllByDateRange(CountryCode.FR, start, end).toMutableList()

list.addAll(
episodeRepository.findAllByDateRange(
CountryCode.FR,
start.minusDays(7),
end.minusDays(7),
list.mapNotNull { anime -> anime.uuid })
)

WeeklyEpisodesDto(
dateTitle,
AbstractConverter.convert(
list.distinctBy { episode -> episode.anime?.uuid },
EpisodeDto::class.java
)
)
}.toList()
}

fun addImage(uuid: UUID, image: String) {
ImageService.add(uuid, ImageService.Type.IMAGE, image, 640, 360)
}
Expand All @@ -71,7 +102,7 @@ class EpisodeService : AbstractService<Episode, EpisodeRepository>() {
val nextSimulcast = simulcasts[2]

val isAnimeReleaseDateTimeBeforeMinusXDays = anime.releaseDateTime.isBefore(adjustedDates[0])
val animeEpisodes = anime.uuid?.let { episodeRepository.findAllByAnime(it).sortedBy { it.releaseDateTime } }
val animeEpisodes = anime.uuid?.let { episodeRepository.findAllByAnime(it).sortedBy { episode -> episode.releaseDateTime } }
val previousEpisode =
animeEpisodes?.lastOrNull { it.releaseDateTime.isBefore(entity.releaseDateTime) && it.episodeType == entity.episodeType && it.langType == entity.langType }
val diff = previousEpisode?.releaseDateTime?.until(entity.releaseDateTime, ChronoUnit.MONTHS) ?: -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import com.google.inject.Inject
import fr.shikkanime.caches.CountryCodeUUIDSortPaginationKeyCache
import fr.shikkanime.dtos.EpisodeDto
import fr.shikkanime.dtos.PageableDto
import fr.shikkanime.dtos.WeeklyEpisodesDto
import fr.shikkanime.entities.Episode
import fr.shikkanime.entities.SortParameter
import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.services.EpisodeService
import fr.shikkanime.utils.MapCache
import java.time.LocalDate
import java.util.*

class EpisodeCacheService : AbstractCacheService {
Expand All @@ -23,11 +25,17 @@ class EpisodeCacheService : AbstractCacheService {
)
}

private val weeklyCache = MapCache<LocalDate, List<WeeklyEpisodesDto>>(classes = listOf(Episode::class.java)) {
episodeService.getWeeklyEpisodes(it)
}

fun findAllBy(
countryCode: CountryCode?,
uuid: UUID?,
sort: List<SortParameter>,
page: Int,
limit: Int
) = cache[CountryCodeUUIDSortPaginationKeyCache(countryCode, uuid, sort, page, limit)]

fun getWeeklyEpisodes(startOfWeekDay: LocalDate) = weeklyCache[startOfWeekDay]
}
12 changes: 6 additions & 6 deletions src/main/kotlin/fr/shikkanime/wrappers/CrunchyrollWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object CrunchyrollWrapper {
body = "grant_type=client_id&client_id=offline_access"
)

require(response.status.value == 200) { "Failed to get anonymous access token" }
require(response.status.value == 200) { "Failed to get anonymous access token (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getAsString("access_token")!!
}
Expand All @@ -59,7 +59,7 @@ object CrunchyrollWrapper {
),
)

require(response.status.value == 200) { "Failed to get CMS" }
require(response.status.value == 200) { "Failed to get CMS (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getNullableJsonObject("cms")?.let {
CMS(
Expand Down Expand Up @@ -102,7 +102,7 @@ object CrunchyrollWrapper {
),
)

require(response.status.value == 200) { "Failed to get media object" }
require(response.status.value == 200) { "Failed to get media object (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("items")?.map { it.asJsonObject }
?: throw Exception("Failed to get media object")
Expand All @@ -116,7 +116,7 @@ object CrunchyrollWrapper {
),
)

require(response.status.value == 200) { "Failed to get seasons" }
require(response.status.value == 200) { "Failed to get seasons (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("items")?.map { it.asJsonObject }
?: throw Exception("Failed to get seasons")
Expand All @@ -130,7 +130,7 @@ object CrunchyrollWrapper {
),
)

require(response.status.value == 200) { "Failed to get episodes" }
require(response.status.value == 200) { "Failed to get episodes (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("items")?.map { it.asJsonObject }
?: throw Exception("Failed to get episodes")
Expand All @@ -144,7 +144,7 @@ object CrunchyrollWrapper {
),
)

require(response.status.value == 200) { "Failed to get simulcasts" }
require(response.status.value == 200) { "Failed to get simulcasts (${response.status.value})" }

return ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("items")?.map { it.asJsonObject }
?: throw Exception("Failed to get simulcasts")
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/fr/shikkanime/wrappers/ThreadsWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class ThreadsWrapper(
val randKey = ByteArray(32).also { secureRandom.nextBytes(it) }
val iv = ByteArray(12).also { secureRandom.nextBytes(it) }
val response = qeSync()
require(response.status.value == 200) { "Failed to get qeSync: ${response.status}" }
require(response.status.value == 200) { "Failed to get qeSync (${response.status})" }
val headers = response.headers
val time = (System.currentTimeMillis() / 1000).toString()

Expand Down Expand Up @@ -153,7 +153,7 @@ class ThreadsWrapper(
body = "params=$params&bk_client_context=$bkClientContext&bloks_versioning_id=$BLOKS_VERSION"
)

require(response.status.value == 200) { "Failed to login: ${response.status}" }
require(response.status.value == 200) { "Failed to login (${response.status})" }

val responseJson = ObjectParser.fromJson(response.bodyAsText())
val rawBloks = responseJson.getAsJsonObject("layout").getAsJsonObject("bloks_payload").getAsJsonObject("tree")
Expand Down Expand Up @@ -245,7 +245,7 @@ class ThreadsWrapper(

if (image != null) {
val uploadImage = uploadImage(username, token, ContentType.Image.PNG, uploadId, image)
require(uploadImage.status.value == 200) { "Failed to upload image: ${uploadImage.status}" }
require(uploadImage.status.value == 200) { "Failed to upload image (${uploadImage.status})" }
}

val map = mutableMapOf<String, Any?>(
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/assets/css/purged/bootstrap.min.css

Large diffs are not rendered by default.

Binary file modified src/main/resources/assets/css/purged/main.min.css
Binary file not shown.
1 change: 1 addition & 0 deletions src/main/resources/templates/_freemarker_implicit.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
[#-- @ftlvariable name="seoLinks" type="kotlin.collections.AbstractList<fr.shikkanime.entities.enums.Link>" --]
[#-- @ftlvariable name="query" type="java.lang.String" --]
[#-- @ftlvariable name="su" type="fr.shikkanime.utils.StringUtils" --]
[#-- @ftlvariable name="weeklyEpisodes" type="kotlin.collections.AbstractList<fr.shikkanime.dtos.WeeklyEpisodesDto>" --]

[#-- @ftlvariable name="analyticsDomain" type="java.lang.String" --]
[#-- @ftlvariable name="analyticsApi" type="java.lang.String" --]
Expand Down
76 changes: 76 additions & 0 deletions src/main/resources/templates/site/calendar.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<#import "_navigation.ftl" as navigation />

<@navigation.display>
<div class="table-responsive">
<table class="table table-dark table-borderless my-5">
<thead>
<tr>
<#list weeklyEpisodes as dailyEpisodes>
<th scope="col" class="text-center" style="width: 14.28%">
${dailyEpisodes.dayOfWeek}
</th>
</#list>
</tr>
</thead>
<tbody>
<tr>
<#list weeklyEpisodes as dailyEpisodes>
<td style="background-color: #060606">
<#list dailyEpisodes.episodes as episode>
<article x-data="{ hover: false }">
<a href="/animes/${episode.anime.slug}" class="text-decoration-none text-white"
@mouseenter="hover = true"
@mouseleave="hover = false">
<div class="position-relative">
<div class="position-relative">
<img src="https://api.shikkanime.fr/v1/attachments?uuid=${episode.anime.uuid}&type=banner"
alt="${su.sanitizeXSS(episode.anime.shortName)} anime banner"
class="img-fluid" width="640"
height="360">

<div class="position-absolute bottom-0 start-0 p-1 px-md-3 bg-black"
id="${episode.uuid}"
data-release-date-time="${episode.releaseDateTime}">
</div>
</div>

<div class="mt-2 d-flex align-items-center">
<img src="https://www.shikkanime.fr/assets/img/platforms/${episode.platform.image}"
alt="${episode.platform.name} platform image"
class="rounded-circle me-2" width="20"
height="20">

<span class="h6 text-truncate-2">
${episode.anime.shortName}
</span>
</div>

<div class="bg-black bg-opacity-75 position-absolute top-0 start-0 w-100 h-100 mh-100 p-3"
x-show="hover">
<#if episode.anime.description??>
<div class="text-truncate-6">
${episode.anime.description}
</div>
</#if>
</div>
</div>
</a>
</article>
</#list>
</td>
</#list>
</tr>
</tbody>
</table>
</div>

<script>
<#-- Get release date and time and convert it to the user's timezone -->
const releaseDateTimeElements = document.querySelectorAll('[data-release-date-time]');
releaseDateTimeElements.forEach(element => {
const releaseDateTime = new Date(element.getAttribute('data-release-date-time'));
element.innerHTML = releaseDateTime.toLocaleTimeString().split(':').slice(0, 2).join(':');
});
</script>
</@navigation.display>

0 comments on commit b65a817

Please sign in to comment.