using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using AutoMapper; using Kavita.API.Attributes; using Kavita.API.Database; using Kavita.API.Services; using Kavita.Common; using Kavita.Models.Constants; using Kavita.Models.DTOs.Theme; using Kavita.Server.Attributes; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Kavita.Server.Controllers; public class ThemeController( IUnitOfWork unitOfWork, IThemeService themeService, ILocalizationService localizationService, IDirectoryService directoryService, IMapper mapper) : BaseApiController { [HttpGet] public async Task>> GetThemes() { return Ok(await unitOfWork.SiteThemeRepository.GetThemeDtos()); } [Authorize(PolicyGroups.AdminPolicy)] [HttpPost("update-default")] public async Task UpdateDefault(UpdateDefaultThemeDto dto) { try { await themeService.UpdateDefault(dto.ThemeId); } catch (KavitaException) { return BadRequest(await localizationService.Translate(UserId, "theme-doesnt-exist")); } return Ok(); } /// /// Returns css content to the UI. UI is expected to escape the content /// /// [AllowAnonymous] [HttpGet("download-content")] public async Task> GetThemeContent(int themeId) { try { return Ok(await themeService.GetContent(themeId)); } catch (KavitaException ex) { return BadRequest(await localizationService.Get("en", ex.Message)); } } /// /// Browse themes that can be used on this server /// /// [HttpGet("browse")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute)] public async Task>> BrowseThemes() { var themes = await themeService.GetDownloadableThemes(); return Ok(themes.Where(t => !t.AlreadyDownloaded)); } /// /// Attempts to delete a theme. If already in use by users, will not allow /// /// /// [HttpDelete] [DisallowRole(PolicyConstants.ReadOnlyRole)] public async Task>> DeleteTheme(int themeId) { await themeService.DeleteTheme(themeId); return Ok(); } /// /// Downloads a SiteTheme from upstream /// /// /// [HttpPost("download-theme")] public async Task> DownloadTheme(DownloadableSiteThemeDto dto) { return Ok(mapper.Map(await themeService.DownloadRepoTheme(dto))); } /// /// Uploads a new theme file /// /// /// [HttpPost("upload-theme")] [DisallowRole(PolicyConstants.ReadOnlyRole)] public async Task> DownloadTheme(IFormFile formFile) { if (!formFile.FileName.EndsWith(".css")) return BadRequest("Invalid file"); if (formFile.FileName.Contains("..")) return BadRequest("Invalid file"); var tempFile = await UploadToTemp(formFile); // Set summary as "Uploaded by Username! on DATE" var theme = await themeService.CreateThemeFromFile(tempFile, Username!); return Ok(mapper.Map(theme)); } private async Task UploadToTemp(IFormFile file) { var outputFile = Path.Join(directoryService.TempDirectory, file.FileName); await using var stream = System.IO.File.Create(outputFile); await file.CopyToAsync(stream); stream.Close(); return outputFile; } }