diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f502bc7d..a1d49dee 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -100,7 +100,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: steps.filter.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch' || startsWith(github.event.ref, 'refs/tags/v') with: context: ${{matrix.context}} diff --git a/DIAGRAMS.md b/DIAGRAMS.md new file mode 100644 index 00000000..40f93ec6 --- /dev/null +++ b/DIAGRAMS.md @@ -0,0 +1,304 @@ +# Diagrams + +# Project Structure +Kyoo is a monorepo that consists of several projects each in their own directory. Diagram below shows an outline of kyoo, projects, and artifacts. + +```mermaid +block-beta + columns 1 + block:proj1:1 + proj_name["Kyoo"]:1 + end + block:proj2:1 + dir_1["autosync/"] + dir_2["back/"] + dir_3["front/"] + dir_4["transcoder/"] + dir_5["scanner/"] + end + block:proj3:1 + %% columns auto (default) + block:autosync_b:1 + autosync_i1("kyoo_autosync") + end + block:back_b:1 + columns 1 + back_i1("kyoo_back") + back_i2("kyoo_migrations") + end + block:front_b:1 + front_i1("kyoo_front") + end + block:transcoder_b:1 + transcoder_i1("kyoo_transcoder") + end + block:scanner_b:1 + columns 1 + scanner_i1("kyoo_scanner") + scanner_i2("kyoo_scanner*") + end + end + + style proj_name fill:transparent,stroke-width:0px + style proj1 fill:#1168bd,stroke-width:0px + style proj2 fill:#1168bd,stroke-width:0px + style proj3 fill:#1168bd,stroke-width:0px + + style dir_1 fill:#438dd5,stroke-width:0px + style dir_2 fill:#438dd5,stroke-width:0px + style dir_3 fill:#438dd5,stroke-width:0px + style dir_4 fill:#438dd5,stroke-width:0px + style dir_5 fill:#438dd5,stroke-width:0px + + style autosync_b fill:#438dd5,stroke-width:0px + style back_b fill:#438dd5,stroke-width:0px + style front_b fill:#438dd5,stroke-width:0px + style transcoder_b fill:#438dd5,stroke-width:0px + style scanner_b fill:#438dd5,stroke-width:0px + + style autosync_i1 fill:#85bbf0,stroke-width:0px + style back_i1 fill:#85bbf0,stroke-width:0px + style back_i2 fill:#85bbf0,stroke-width:0px + style front_i1 fill:#85bbf0,stroke-width:0px + style transcoder_i1 fill:#85bbf0,stroke-width:0px + style scanner_i1 fill:#85bbf0,stroke-width:0px + style scanner_i2 fill:#85bbf0,stroke-width:0px +``` + +# C4 Diagrams +Diagrams that focus on capturing project from a high level point of view. Context, Container, Component, Code + +## Context +```mermaid +C4Context + UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2") + + title Context Diagram for Kyoo + + Person(user, "User") + System(kyoo, "Kyoo", "") + System_Ext(media, "MediaLibrary", "") + System_Ext(content, "ContentDatabase", "") + System_Ext(tracker, "ActivityTracker", "") + + Rel(user, kyoo, "") + Rel(kyoo, content, "") + Rel(kyoo, media, "") + Rel(kyoo, tracker, "") +``` + +## Container +Messaging is middleware. EnterpriseMessageBus is for any messaging handled between different projects. +```mermaid +C4Container + UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="3") + + title Container diagram for Kyoo System + + Person(user, "User") + System_Boundary(internal, "Kyoo") { + Container(frontend, "front/") + Container(backend, "back/") + Container(transcoder, "transcoder/") + Container(scanner, "scanner/") + ContainerQueue(emb, "emb", "", "EnterpriseMessageBus") + Container(autosync, "autosync/") + } + System_Boundary(external, "") { + System_Ext(content, "ContentDatabase", "") + } + System_Boundary(external2, "") { + System_Ext(tracker, "ActivityTracker", "") + } + System_Boundary(external3, "") { + System_Ext(media, "MediaLibrary", "") + } + + Rel(user, frontend, "") + Rel(user, backend, "") + Rel(frontend, backend, "") + Rel(backend, emb, "") + Rel(backend, media, "") + Rel(backend, transcoder, "") + Rel_Back(autosync, emb, "") + Rel(autosync, tracker, "") + Rel_Back(scanner, emb, "") + Rel(scanner, backend, "") + Rel(scanner, media, "") + Rel(scanner, content, "") + Rel(transcoder, media, "") +``` + + +## Component +### Autosync +```mermaid +C4Component + UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="2") + + title Component Diagram for Autosync + + Container_Boundary(autosync, "autosync") { + Component(autosync_c1, "kyoo_autosync", "python, python3.12", "") + } + Container_Boundary(emb, "emb") { + ComponentQueue(emb_q1, "autosync", "RabbitMQ, Queue", "") + ComponentQueue(emb_e1, "events.watched", "RabbitMQ, Exchange", "") + + } + Container_Boundary(tracker, "ActivityTracker") { + Component_Ext(tracker_c1, "TrackerProvider", "API", "simkl") + } + Container_Boundary(backend, "back") { + Component(backend_c2, "kyoo_back", "C#, .NET 8.0", "API Backend") + } + + Rel(emb_e1, emb_q1, "bound") + Rel_Back(autosync_c1, emb_q1, "consumes") + Rel(backend_c2, emb_e1, "produces") + Rel(autosync_c1, tracker_c1, "updates") +``` + +### Back +```mermaid +C4Component + UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="3") + + title Component Diagram for Back + + Person(user, "User") + + Container_Boundary(frontend, "front") { + Component(frontend_c1, "kyoo_front", "typescript, node.js", "Static Content") + } + Container_Boundary(backend, "back") { + Component(backend_c1, "kyoo_migrations", "C#, .NET 8.0", "Postgres Migration") + ComponentDb(backend_db1, "backend", "Postgres", "user data and session state") + Component(backend_c3, "BackendMetadata", "Volume", "Persistent. Distributed Metadata") + ComponentDb(backend_db2, "search", "Meilisearch", "search resource") + Component(backend_c2, "kyoo_back", "C#, .NET 8.0", "API Backend") + } + + Container_Boundary(media, "MediaLibrary") { + Component_Ext(media_c1, "MediaShare", "Volume", "Read Only") + } + Container_Boundary(transcoder, "transcoder") { + Component(transcoder_c1, "kyoo_transcoder", "go, go", "Video Transcoder") + } + Container_Boundary(emb, "emb") { + ComponentQueue(emb_e1, "events.watched", "RabbitMQ, Exchange", "") + ComponentQueue(emb_q2, "scanner.rescan", "RabbitMQ, Queue", "") + ComponentQueue(emb_q1, "autosync", "RabbitMQ, Queue", "") + ComponentQueue(emb_e2, "events.resource", "RabbitMQ, Exchange", "unused") + } + + Container_Boundary(scanner, "scanner") { + Component(scanner_c1, "kyoo_scanner", "python, python3.12", "scanner") + Component(scanner_c2, "kyoo_scanner", "python, python3.12", "matcher") + } + + Container_Boundary(autosync, "autosync") { + Component(autosync_c1, "kyoo_autosync", "python, python3.12", "") + } + + + Rel(user, backend_c2, "") + Rel(backend_c1, backend_db1, "") + Rel(backend_c2, backend_db1, "") + Rel(backend_c2, backend_db2, "") + Rel(backend_c2, media_c1, "") + Rel(backend_c2, transcoder_c1, "") + Rel(backend_c2, backend_c3, "") + Rel(backend_c2, emb_q2, "produces") + Rel(backend_c2, emb_e1, "produces") + Rel(backend_c2, emb_e2, "produces") + Rel(emb_e1, emb_q1, "bound") + Rel_Back(autosync_c1, emb_q1, "consumes") + Rel_Back(scanner_c1, emb_q2, "consumes") + Rel(scanner_c1, backend_c2, "") + Rel(scanner_c2, backend_c2, "") + Rel(frontend_c1, backend_c2, "") +``` + +### Front +```mermaid +C4Component + UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="2") + + title Component Diagram for Front + + Person(user, "User") + Container_Boundary(frontend, "front") { + Component(frontend_c1, "kyoo_front", "typescript, node.js", "Static Content") + } + Container_Boundary(backend, "back") { + Component(backend_c2, "kyoo_back", "C#, .NET 8.0", "API Backend") + } + + Rel(frontend_c1, backend_c2, "ssr") + Rel(user, frontend_c1, "") +``` + +### Scanner +```mermaid +C4Component + UpdateLayoutConfig($c4ShapeInRow="5", $c4BoundaryInRow="3") + + title Component Diagram for Scanner + + Container_Boundary(media, "MediaLibrary") { + Component_Ext(media_c1, "MediaShare", "Volume", "Read Only") + } + + Container_Boundary(content, "ContentDatabase") { + Component_Ext(content_c1, "ContentProvider", "API", "tmdb or tvdb") + } + + Container_Boundary(scanner, "scanner") { + Component(scanner_c2, "kyoo_scanner", "python, python3.12", "matcher") + ComponentQueue(scanner_q1, "scanner", "RabbitMQ, Queue", "") + Component(scanner_c1, "kyoo_scanner", "python, python3.12", "scanner") + } + + Container_Boundary(emb, "emb") { + ComponentQueue(emb_q2, "scanner.rescan", "RabbitMQ, Queue", "") + } + + Container_Boundary(backend, "back") { + Component(backend_c2, "kyoo_back", "C#, .NET 8.0", "API Backend") + } + + Rel(scanner_c1, scanner_q1, "produces") + Rel(scanner_c1, media_c1, "watches") + Rel(scanner_c1, backend_c2, "Fetch existing scans") + Rel(scanner_c2, content_c1, "Fetch media data") + Rel(scanner_c2, backend_c2, "Pushes media data") + Rel_Back(scanner_c2, scanner_q1, "consumes") + Rel_Back(scanner_c1, emb_q2, "consumes") + Rel(backend_c2, emb_q2, "produces") +``` + +### Transcoder +```mermaid +C4Component + UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2") + + title Component Diagram for Transcoder + + Container_Boundary(transcoder, "transcoder") { + Component(transcoder_c2, "TranscodeMetadata", "Volume", "Persistent. Distributed Metadata") + Component(transcoder_c1, "kyoo_transcoder", "go, go", "Video Transcoder") + Component(transcoder_c3, "TranscodeCache", "Volume", "Volatile. Local cache") + } + Container_Boundary(media, "MediaLibrary") { + Component_Ext(media_c1, "MediaShare", "Volume", "Read Only") + } + Container_Boundary(backend, "back") { + Component(backend_c2, "kyoo_back", "C#, .NET 8.0", "API Backend") + } + + Rel(transcoder_c1, media_c1, "mounts") + Rel(transcoder_c1, transcoder_c2, "") + Rel(transcoder_c1, transcoder_c3, "") + Rel(backend_c2, transcoder_c1, "") +``` \ No newline at end of file diff --git a/back/src/Kyoo.Core/Views/Helper/Transcoder.cs b/back/src/Kyoo.Core/Views/Helper/Transcoder.cs index 43f25a8d..6a7ceb84 100644 --- a/back/src/Kyoo.Core/Views/Helper/Transcoder.cs +++ b/back/src/Kyoo.Core/Views/Helper/Transcoder.cs @@ -27,6 +27,7 @@ using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; namespace Kyoo.Core.Api; @@ -54,7 +55,7 @@ public abstract class TranscoderApi(IRepository repository) : CrudThumbsAp private async Task _GetPath64(Identifier identifier) { string path = await GetPath(identifier); - return Convert.ToBase64String(Encoding.UTF8.GetBytes(path)); + return WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(path)); } /// diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 98f16247..88aab714 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -131,7 +131,7 @@ services: profiles: ['qsv'] traefik: - image: traefik:v3.0 + image: traefik:v3.1 restart: on-failure command: - "--providers.docker=true" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 599965f5..a735b0af 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -155,7 +155,7 @@ services: profiles: ['qsv'] traefik: - image: traefik:v3.0 + image: traefik:v3.1 restart: on-failure command: - "--providers.docker=true" diff --git a/docker-compose.yml b/docker-compose.yml index 3f33d33b..9bffedca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -130,7 +130,7 @@ services: profiles: ['qsv'] traefik: - image: traefik:v3.0 + image: traefik:v3.1 restart: unless-stopped command: - "--providers.docker=true" diff --git a/front/packages/ui/src/details/episode.tsx b/front/packages/ui/src/details/episode.tsx index 4435cee7..0b887b74 100644 --- a/front/packages/ui/src/details/episode.tsx +++ b/front/packages/ui/src/details/episode.tsx @@ -259,7 +259,7 @@ export const EpisodeLine = ({ width: percent(18), aspectRatio: 16 / 9, }} - {...css({ flexShrink: 0, m: ts(1) })} + {...css({ flexShrink: 0, m: ts(1), borderRadius: imageBorderRadius })} > {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( <> diff --git a/front/translations/tr.json b/front/translations/tr.json index 31061ea0..1b2631ff 100644 --- a/front/translations/tr.json +++ b/front/translations/tr.json @@ -59,7 +59,15 @@ "desc": "azalan" }, "switchToGrid": "Izgara görünümüne geç", - "switchToList": "Liste görünümüne geç" + "switchToList": "Liste görünümüne geç", + "mediatypekey": { + "collection": "Koleksiyonlar", + "show": "Diziler", + "all": "Hepsi", + "movie": "Filmler" + }, + "mediatype-tt": "İçeriğin Türü", + "mediatypelabel": "İçeriğin Türü" }, "misc": { "settings": "Ayarlar", diff --git a/renovate.json5 b/renovate.json5 index a6430424..91643799 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -1,6 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended", ":disableRateLimiting", "regexManagers:biomeVersions"], + "extends": ["config:recommended", ":disableRateLimiting", "customManagers:biomeVersions"], "minimumReleaseAge": "5 days", "ignorePaths": ["**/front/**"], "packageRules": [ diff --git a/transcoder/utils.go b/transcoder/utils.go index 9feaee4f..cd88c545 100644 --- a/transcoder/utils.go +++ b/transcoder/utils.go @@ -24,9 +24,9 @@ func GetPath(c echo.Context) (string, string, error) { if key == "" { return "", "", echo.NewHTTPError(http.StatusBadRequest, "Missing resouce path.") } - pathb, err := base64.StdEncoding.DecodeString(key) + pathb, err := base64.RawURLEncoding.DecodeString(key) if err != nil { - return "", "", echo.NewHTTPError(http.StatusBadRequest, "Invalid path. Should be base64 encoded.") + return "", "", echo.NewHTTPError(http.StatusBadRequest, "Invalid path. Should be base64url (without padding) encoded.") } path := filepath.Clean(string(pathb)) if !filepath.IsAbs(path) {