diff --git a/flake.nix b/flake.nix index 2645a68..114ed09 100644 --- a/flake.nix +++ b/flake.nix @@ -68,5 +68,50 @@ ) { }; } ); + + nixosModules.default = + { config, lib, pkgs, ... }: + let + cfg = config.services.jellyfin-lessons; + in + { + options.services.jellyfin-lessons = { + enable = lib.mkEnableOption "Jellyfin Lessons plugin"; + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/jellyfin-lessons"; + description = "Directory for lessons database"; + }; + adminPort = lib.mkOption { + type = lib.types.port; + default = 3099; + description = "Port for admin UI"; + }; + }; + + config = lib.mkIf cfg.enable { + # Copy plugin DLL to Jellyfin plugins directory on jellyfin preStart + systemd.services.jellyfin = { + preStart = let + pluginPkg = self.packages.${pkgs.system}.default; + in '' + mkdir -p /var/lib/jellyfin/plugins/Lessons + cp -f ${pluginPkg}/lib/jellyfin-lessons/*.dll /var/lib/jellyfin/plugins/Lessons/ + ''; + }; + + # Admin UI as a user systemd service + systemd.user.services.jellyfin-lessons-admin = { + description = "Jellyfin Lessons Admin UI"; + after = [ "network.target" ]; + wantedBy = [ "default.target" ]; + serviceConfig = { + ExecStart = "${pkgs.nodejs}/bin/node ${self}/admin-ui/server.js"; + Restart = "on-failure"; + Environment = "PORT=${toString cfg.adminPort}"; + }; + }; + }; + }; }; } diff --git a/plugin/Api/LessonsController.cs b/plugin/Api/LessonsController.cs new file mode 100644 index 0000000..d2f53c8 --- /dev/null +++ b/plugin/Api/LessonsController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Mvc; +using Jellyfin.Plugin.Lessons.Data; + +namespace Jellyfin.Plugin.Lessons.Api; + +[ApiController] +[Route("Plugins/Lessons")] +public class LessonsController : ControllerBase +{ + private readonly LessonStore _store; + + public LessonsController(LessonStore store) + { + _store = store; + } + + [HttpGet("List")] + public IActionResult List() + { + var lessons = _store.GetAllLessons(); + + var summaries = lessons.Select(l => new + { + id = l.Id, + title = l.Title, + grade = l.Grade, + subject = l.Subject, + lessonDate = l.LessonDate, + description = l.Description + }); + + return Ok(summaries); + } + + [HttpGet("Detail")] + public IActionResult Detail([FromQuery] string id) + { + if (string.IsNullOrEmpty(id)) + return BadRequest("Lesson id is required."); + + var lesson = _store.GetLessonById(id); + if (lesson == null) + return NotFound(); + + return Ok(new + { + id = lesson.Id, + title = lesson.Title, + grade = lesson.Grade, + subject = lesson.Subject, + lessonDate = lesson.LessonDate, + description = lesson.Description, + sourcePath = lesson.SourcePath, + compiledHtml = lesson.CompiledHtml, + createdAt = lesson.CreatedAt, + updatedAt = lesson.UpdatedAt, + mediaItems = lesson.MediaItems.Select(m => new + { + lessonId = m.LessonId, + jellyfinItemId = m.JellyfinItemId, + sortOrder = m.SortOrder + }) + }); + } +} diff --git a/plugin/Configuration/lessonsPage.html b/plugin/Configuration/lessonsPage.html new file mode 100644 index 0000000..c226509 --- /dev/null +++ b/plugin/Configuration/lessonsPage.html @@ -0,0 +1,460 @@ + + +
+ + +