using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using NoticeBoard.Data; using Ical.Net; using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; namespace NoticeBoard.Controllers; [Authorize] public class ApiController : Controller { private readonly IWebHostEnvironment _env; private readonly IConfiguration _config; private readonly IHttpClientFactory _httpClientFactory; public ApiController(IWebHostEnvironment env, IConfiguration config, IHttpClientFactory httpClientFactory) { _env = env; _config = config; _httpClientFactory = httpClientFactory; } [HttpPost] public async Task Upload(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { error = "No file provided." }); var maxSize = _config.GetValue("MaxUploadSizeMB", 20) * 1024 * 1024; if (file.Length > maxSize) return BadRequest(new { error = $"File too large. Maximum {maxSize / 1024 / 1024}MB." }); var allowedExts = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico" }; var ext = Path.GetExtension(file.FileName).ToLower(); if (!allowedExts.Contains(ext)) return BadRequest(new { error = "File type not allowed. Use: " + string.Join(", ", allowedExts) }); var uploadsPath = Path.Combine(_env.WebRootPath, "uploads"); if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); var fileName = $"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid():N}{ext}"; var filePath = Path.Combine(uploadsPath, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Json(new { location = $"/uploads/{fileName}" }); } [HttpPost] public async Task UploadFile(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { error = "No file provided." }); var uploadsPath = Path.Combine(_env.WebRootPath, "uploads"); if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath); var ext = Path.GetExtension(file.FileName).ToLower(); var fileName = $"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid():N}{ext}"; var filePath = Path.Combine(uploadsPath, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Json(new { url = $"/uploads/{fileName}", name = file.FileName, size = file.Length }); } [HttpGet] public IActionResult ListUploads() { var uploadsPath = Path.Combine(_env.WebRootPath, "uploads"); if (!Directory.Exists(uploadsPath)) return Json(new { files = Array.Empty() }); var imageExts = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico" }; var files = Directory.GetFiles(uploadsPath) .Select(f => new FileInfo(f)) .Where(f => imageExts.Contains(f.Extension.ToLower())) .OrderByDescending(f => f.CreationTimeUtc) .Select(f => new { title = f.Name, value = $"/uploads/{f.Name}" }); return Json(files); } [HttpPost] public IActionResult DeleteUpload([FromBody] DeleteFileRequest request) { if (string.IsNullOrEmpty(request?.FileName)) return BadRequest(); var filePath = Path.Combine(_env.WebRootPath, "uploads", Path.GetFileName(request.FileName)); if (System.IO.File.Exists(filePath)) { System.IO.File.Delete(filePath); return Ok(); } return NotFound(); } [AllowAnonymous] [HttpGet] public async Task ParseIcs(string url) { if (string.IsNullOrWhiteSpace(url)) return BadRequest(new { error = "No URL provided." }); try { string icsContent; if (url.StartsWith("/uploads/")) { var filePath = Path.Combine(_env.WebRootPath, url.TrimStart('/')); if (!System.IO.File.Exists(filePath)) return NotFound(new { error = "ICS file not found." }); icsContent = await System.IO.File.ReadAllTextAsync(filePath); } else { var client = _httpClientFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(15); icsContent = await client.GetStringAsync(url); } var calendar = Calendar.Load(icsContent); var now = DateTime.Now; var upcoming = calendar.Events .Where(e => e.DtEnd != null ? e.DtEnd.AsSystemLocal >= now : e.DtStart.AsSystemLocal >= now.AddDays(-1)) .OrderBy(e => e.DtStart.AsSystemLocal) .Take(20) .Select(e => new { summary = e.Summary, description = e.Description, location = e.Location, start = e.DtStart?.AsSystemLocal.ToString("yyyy-MM-dd HH:mm"), end = e.DtEnd?.AsSystemLocal.ToString("yyyy-MM-dd HH:mm"), allDay = e.IsAllDay }); return Json(new { events = upcoming }); } catch (Exception ex) { return BadRequest(new { error = $"Failed to parse ICS: {ex.Message}" }); } } } public class DeleteFileRequest { public string? FileName { get; set; } }