From a86388044b4762aefe6052950fbccbb2108603f7 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 20 May 2026 15:15:33 +1000 Subject: [PATCH] =?UTF-8?q?v1.0.0:=20ApiController=20=E2=80=94=20uploads,?= =?UTF-8?q?=20ICS=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/ApiController.cs | 162 +++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 Controllers/ApiController.cs diff --git a/Controllers/ApiController.cs b/Controllers/ApiController.cs new file mode 100644 index 0000000..5b65208 --- /dev/null +++ b/Controllers/ApiController.cs @@ -0,0 +1,162 @@ +using Microsoft.AspNetCore.Mvc; +using NoticeBoard.Data; +using Ical.Net; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; + +namespace NoticeBoard.Controllers; + +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 files = Directory.GetFiles(uploadsPath) + .Select(f => new FileInfo(f)) + .OrderByDescending(f => f.CreationTimeUtc) + .Select(f => new + { + name = f.Name, + url = $"/uploads/{f.Name}", + size = f.Length, + created = f.CreationTimeUtc + }); + + return Json(new { 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(); + } + + [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; } +}