Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ val javaImageScalingVersion = "0.8.6"
val firebaseVersion = "9.5.0"
val simpleJavaMailVersion = "8.12.6"
val jacksonVersion = "2.19.2"
val openTelemetry = "1.52.0"

val jdaVersion = "6.0.0-preview"
val twitter4jVersion = "4.0.7"
Expand Down Expand Up @@ -91,6 +92,13 @@ dependencies {
implementation("com.google.firebase:firebase-admin:$firebaseVersion")
implementation("org.simplejavamail:simple-java-mail:$simpleJavaMailVersion")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion")

implementation("io.opentelemetry:opentelemetry-api:$openTelemetry")
implementation("io.opentelemetry:opentelemetry-sdk:$openTelemetry")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:$openTelemetry")
implementation("io.opentelemetry:opentelemetry-exporter-logging:$openTelemetry")
implementation("io.opentelemetry:opentelemetry-context:$openTelemetry")

implementation("net.dv8tion:JDA:$jdaVersion")
implementation("org.twitter4j:twitter4j-core:$twitter4jVersion")
implementation("twitter4j-v2:twitter4j-v2-support") {
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/fr/shikkanime/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private val logger = LoggerFactory.getLogger(Constant.NAME)

fun main(args: Array<String>) {
logger.info("Starting ${Constant.NAME}...")
TelemetryConfig.initialize()

logger.info("Testing Playwright installation...")
checkPlaywrightInstallation()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import fr.shikkanime.entities.enums.LangType
import fr.shikkanime.services.caches.AnimeCacheService
import fr.shikkanime.services.caches.MemberFollowAnimeCacheService
import fr.shikkanime.utils.StringUtils
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
import fr.shikkanime.utils.atStartOfWeek
import fr.shikkanime.utils.routes.*
import fr.shikkanime.utils.routes.method.Get
Expand All @@ -17,6 +19,7 @@ import java.util.*

@Controller("/api/v1/animes")
class AnimeController : HasPageableRoute() {
private val tracer = TelemetryConfig.getTracer("AnimeController")
@Inject private lateinit var animeCacheService: AnimeCacheService
@Inject private lateinit var memberFollowAnimeCacheService: MemberFollowAnimeCacheService

Expand Down Expand Up @@ -46,17 +49,17 @@ class AnimeController : HasPageableRoute() {
desc
)

return Response.ok(
if (memberUuid != null) {
memberFollowAnimeCacheService.findAllBy(memberUuid, page, limit)
} else {
if (!name.isNullOrBlank()) {
return tracer.trace {
Response.ok(
if (memberUuid != null) {
memberFollowAnimeCacheService.findAllBy(memberUuid, page, limit)
} else if (!name.isNullOrBlank()) {
animeCacheService.findAllByName(countryCode, name, page, limit, searchTypes)
} else {
animeCacheService.findAllBy(countryCode, simulcastUuid, sortParams, page, limit, searchTypes)
}
}
)
)
}
}

@Path("/weekly")
Expand All @@ -74,7 +77,7 @@ class AnimeController : HasPageableRoute() {
return Response.badRequest(MessageDto.error("Invalid week format"))
}.atStartOfWeek()

return Response.ok(animeCacheService.getWeeklyAnimes(country, memberUuid, startOfWeekDay, searchTypes))
return tracer.trace { Response.ok(animeCacheService.getWeeklyAnimes(country, memberUuid, startOfWeekDay, searchTypes)) }
}

@Path("/missed")
Expand All @@ -86,6 +89,6 @@ class AnimeController : HasPageableRoute() {
@QueryParam("limit", "9") limitParam: Int
): Response {
val (page, limit, _) = pageableRoute(pageParam, limitParam, null, null)
return Response.ok(memberFollowAnimeCacheService.getMissedAnimes(uuid, page, limit))
return tracer.trace { Response.ok(memberFollowAnimeCacheService.getMissedAnimes(uuid, page, limit)) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import fr.shikkanime.entities.enums.ImageType
import fr.shikkanime.services.AttachmentService
import fr.shikkanime.services.caches.AttachmentCacheService
import fr.shikkanime.utils.Constant
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
import fr.shikkanime.utils.routes.Cached
import fr.shikkanime.utils.routes.Controller
import fr.shikkanime.utils.routes.Path
Expand All @@ -17,6 +19,7 @@ import java.util.*

@Controller("/api/v1/attachments")
class AttachmentController {
private val tracer = TelemetryConfig.getTracer("AttachmentController")
@Inject private lateinit var attachmentService: AttachmentService
@Inject private lateinit var attachmentCacheService: AttachmentCacheService

Expand All @@ -27,29 +30,31 @@ class AttachmentController {
@QueryParam uuid: UUID?,
@QueryParam("type") typeString: String?
): Response {
if (uuid == null || runCatching { UUID.fromString(uuid.toString()) }.isFailure)
Response.badRequest(MessageDto.error("UUID is required"))
return tracer.trace {
if (uuid == null || runCatching { UUID.fromString(uuid.toString()) }.isFailure)
return@trace Response.badRequest(MessageDto.error("UUID is required"))

val type = ImageType.entries.find { it.name.equals(typeString, true) }
?: return Response.badRequest(MessageDto.error("Invalid type"))
val type = ImageType.entries.find { it.name.equals(typeString, true) }
?: return@trace Response.badRequest(MessageDto.error("Invalid type"))

// Get attachment from cache or database
val attachment = attachmentCacheService.findByEntityUuidTypeAndActive(uuid!!, type)
?: return Response.notFound(MessageDto.error("Attachment not found"))
// Get attachment from cache or database
val attachment = attachmentCacheService.findByEntityUuidTypeAndActive(uuid, type)
?: return@trace Response.notFound(MessageDto.error("Attachment not found"))

// Check if image is in the memory cache
val imageBytes = attachmentService.getContentFromCache(attachment) ?: run {
val file = attachmentService.getFile(attachment)
// Check if image is in the memory cache
val imageBytes = attachmentService.getContentFromCache(attachment) ?: run {
val file = attachmentService.getFile(attachment)

if (!file.exists() || file.length() <= 0 || attachment.uuid in attachmentService.inProgressAttachments)
return Response.notFound(MessageDto.error("Attachment not found"))
if (!file.exists() || file.length() <= 0 || attachment.uuid in attachmentService.inProgressAttachments)
return@trace Response.notFound(MessageDto.error("Attachment not found"))

// Read the file and store it in the cache
val bytes = file.readBytes()
attachmentService.setContentInCache(attachment, bytes)
bytes
}
// Read the file and store it in the cache
val bytes = file.readBytes()
attachmentService.setContentInCache(attachment, bytes)
bytes
}

return Response.multipart(imageBytes, ContentType.parse("image/webp"))
return@trace Response.multipart(imageBytes, ContentType.parse("image/webp"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import fr.shikkanime.services.caches.EpisodeMappingCacheService
import fr.shikkanime.services.caches.MemberFollowEpisodeCacheService
import fr.shikkanime.utils.EncryptionManager
import fr.shikkanime.utils.StringUtils
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
import fr.shikkanime.utils.routes.*
import fr.shikkanime.utils.routes.method.Get
import fr.shikkanime.utils.routes.param.QueryParam
Expand All @@ -18,6 +20,7 @@ import javax.imageio.ImageIO

@Controller("/api/v1/episode-mappings")
class EpisodeMappingController : HasPageableRoute() {
private val tracer = TelemetryConfig.getTracer("EpisodeMappingController")
@Inject private lateinit var episodeMappingCacheService: EpisodeMappingCacheService
@Inject private lateinit var episodeVariantService: EpisodeVariantService
@Inject private lateinit var memberFollowEpisodeCacheService: MemberFollowEpisodeCacheService
Expand All @@ -42,13 +45,15 @@ class EpisodeMappingController : HasPageableRoute() {
desc
)

return Response.ok(
if (memberUuid != null) {
memberFollowEpisodeCacheService.findAllBy(memberUuid, page, limit)
} else {
episodeMappingCacheService.findAllBy(countryCode, animeUuid, season, sortParameters, page, limit)
}
)
return tracer.trace {
Response.ok(
if (memberUuid != null) {
memberFollowEpisodeCacheService.findAllBy(memberUuid, page, limit)
} else {
episodeMappingCacheService.findAllBy(countryCode, animeUuid, season, sortParameters, page, limit)
}
)
}
}

@Path("/media-image")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import fr.shikkanime.dtos.MessageDto
import fr.shikkanime.entities.Member
import fr.shikkanime.services.MemberActionService
import fr.shikkanime.utils.MapCache
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
import fr.shikkanime.utils.routes.Controller
import fr.shikkanime.utils.routes.JWTAuthenticated
import fr.shikkanime.utils.routes.Path
Expand All @@ -16,6 +18,7 @@ import java.util.*

@Controller("/api/v1/member-actions")
class MemberActionController {
private val tracer = TelemetryConfig.getTracer("MemberActionController")
@Inject private lateinit var memberActionService: MemberActionService

@Path("/validate")
Expand All @@ -31,7 +34,7 @@ class MemberActionController {
return Response.badRequest(MessageDto.error("Action is required"))

try {
memberActionService.validateAction(uuid!!, action)
tracer.trace { memberActionService.validateAction(uuid!!, action) }
MapCache.invalidate(Member::class.java)
return Response.ok()
} catch (e: Exception) {
Expand Down
61 changes: 36 additions & 25 deletions src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import fr.shikkanime.services.MemberService
import fr.shikkanime.services.caches.MemberCacheService
import fr.shikkanime.utils.MapCache
import fr.shikkanime.utils.StringUtils
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
import fr.shikkanime.utils.routes.*
import fr.shikkanime.utils.routes.method.Delete
import fr.shikkanime.utils.routes.method.Get
Expand All @@ -25,6 +27,7 @@ import java.util.*

@Controller("/api/v1/members")
class MemberController : HasPageableRoute() {
private val tracer = TelemetryConfig.getTracer("MemberController")
@Inject private lateinit var memberService: MemberService
@Inject private lateinit var memberCacheService: MemberCacheService
@Inject private lateinit var memberFollowAnimeService: MemberFollowAnimeService
Expand All @@ -33,15 +36,17 @@ class MemberController : HasPageableRoute() {
@Path("/register")
@Post
private fun registerMember(): Response {
var identifier: String
return tracer.trace {
var identifier: String

do {
identifier = StringUtils.generateRandomString(12)
} while (memberService.findByIdentifier(identifier) != null)
do {
identifier = StringUtils.generateRandomString(12)
} while (memberService.findByIdentifier(identifier) != null)

memberService.register(identifier)
MapCache.invalidate(Member::class.java)
return Response.created(mapOf("identifier" to identifier))
memberService.register(identifier)
MapCache.invalidate(Member::class.java)
Response.created(mapOf("identifier" to identifier))
}
}

@Path("/login")
Expand All @@ -51,9 +56,11 @@ class MemberController : HasPageableRoute() {
@HttpHeader("X-Device") device: String?,
@HttpHeader("X-Locale") locale: String?,
@BodyParam identifier: String
) = memberService.login(identifier, appVersion, device, locale)?.let { Response.ok(it) } ?: runBlocking {
delay(1000)
Response.notFound(MessageDto.error("Member not found"))
) = tracer.trace {
memberService.login(identifier, appVersion, device, locale)?.let { Response.ok(it) } ?: runBlocking {
delay(1000)
Response.notFound(MessageDto.error("Member not found"))
}
}

@Path("/associate-email")
Expand Down Expand Up @@ -99,39 +106,39 @@ class MemberController : HasPageableRoute() {
private fun followAnime(
@JWTUser memberUuid: UUID,
@BodyParam anime: GenericDto
) = memberFollowAnimeService.follow(memberUuid, anime)
) = tracer.trace { memberFollowAnimeService.follow(memberUuid, anime) }

@Path("/animes")
@Delete
@JWTAuthenticated
private fun unfollowAnime(
@JWTUser memberUuid: UUID,
@BodyParam anime: GenericDto
) = memberFollowAnimeService.unfollow(memberUuid, anime)
) = tracer.trace { memberFollowAnimeService.unfollow(memberUuid, anime) }

@Path("/follow-all-episodes")
@Put
@JWTAuthenticated
private fun followAllEpisodes(
@JWTUser memberUuid: UUID,
@BodyParam anime: GenericDto
) = memberFollowEpisodeService.followAll(memberUuid, anime)
) = tracer.trace { memberFollowEpisodeService.followAll(memberUuid, anime) }

@Path("/episodes")
@Put
@JWTAuthenticated
private fun followEpisode(
@JWTUser memberUuid: UUID,
@BodyParam episode: GenericDto
) = memberFollowEpisodeService.follow(memberUuid, episode)
) = tracer.trace { memberFollowEpisodeService.follow(memberUuid, episode) }

@Path("/episodes")
@Delete
@JWTAuthenticated
private fun unfollowEpisode(
@JWTUser memberUuid: UUID,
@BodyParam episode: GenericDto
) = memberFollowEpisodeService.unfollow(memberUuid, episode)
) = tracer.trace { memberFollowEpisodeService.unfollow(memberUuid, episode) }

@Path("/image")
@Post
Expand All @@ -140,14 +147,16 @@ class MemberController : HasPageableRoute() {
@JWTUser memberUuid: UUID,
@BodyParam multiPartData: MultiPartData
): Response {
try {
runBlocking { memberService.changeProfileImage(memberCacheService.find(memberUuid)!!, multiPartData) }
} catch (e: Exception) {
return Response.badRequest(MessageDto.error(e.message ?: "Invalid file format"))
}

MapCache.invalidate(Member::class.java)
return Response.ok()
return tracer.trace {
try {
runBlocking { memberService.changeProfileImage(memberCacheService.find(memberUuid)!!, multiPartData) }
} catch (e: Exception) {
return@trace Response.badRequest(MessageDto.error(e.message ?: "Invalid file format"))
}

MapCache.invalidate(Member::class.java)
Response.ok()
}
}

@Path("/refresh")
Expand All @@ -157,7 +166,9 @@ class MemberController : HasPageableRoute() {
@JWTUser memberUuid: UUID,
@QueryParam("limit", "9") limitParam: Int
): Response {
val (_, limit, _) = pageableRoute(null, limitParam, null, null)
return Response.ok(memberCacheService.getRefreshMember(memberUuid, limit) ?: return Response.notFound())
return tracer.trace {
val (_, limit, _) = pageableRoute(null, limitParam, null, null)
Response.ok(memberCacheService.getRefreshMember(memberUuid, limit) ?: return@trace Response.notFound())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ package fr.shikkanime.controllers.api

import com.google.inject.Inject
import fr.shikkanime.services.caches.SimulcastCacheService
import fr.shikkanime.utils.TelemetryConfig
import fr.shikkanime.utils.TelemetryConfig.trace
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

@Controller("/api/v1/simulcasts")
class SimulcastController {
private val tracer = TelemetryConfig.getTracer("SimulcastController")
@Inject private lateinit var simulcastCacheService: SimulcastCacheService

@Path
@Get
private fun getAll() = Response.ok(simulcastCacheService.findAll())
private fun getAll() = tracer.trace { Response.ok(simulcastCacheService.findAll()) }
}
Loading
Loading