From bee85bf92aab84087798ffa9f3f16336acef2fce Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Thu, 19 Mar 2026 18:56:34 +0100 Subject: Basic backoffice for alert management --- .../Controllers/ArrivalsController.cs | 2 +- .../Controllers/Backoffice/AlertsApiController.cs | 14 ++++ .../Controllers/Backoffice/AlertsController.cs | 83 ++++++++++++++++++++++ .../Controllers/Backoffice/BackofficeController.cs | 18 +++++ .../Controllers/Backoffice/LoginController.cs | 28 ++++++++ src/Enmarcha.Backend/Controllers/TileController.cs | 2 +- 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/Enmarcha.Backend/Controllers/Backoffice/AlertsApiController.cs create mode 100644 src/Enmarcha.Backend/Controllers/Backoffice/AlertsController.cs create mode 100644 src/Enmarcha.Backend/Controllers/Backoffice/BackofficeController.cs create mode 100644 src/Enmarcha.Backend/Controllers/Backoffice/LoginController.cs (limited to 'src/Enmarcha.Backend/Controllers') diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs index 8ce63f7..f922ca9 100644 --- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs +++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs @@ -168,7 +168,7 @@ public partial class ArrivalsController : ControllerBase List arrivals = []; foreach (var item in stop.Arrivals) { - //if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None)) continue; + if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None)) continue; if ( item.Trip.ArrivalStoptime.Stop.GtfsId == id && item.Trip.DepartureStoptime.Stop.GtfsId != id diff --git a/src/Enmarcha.Backend/Controllers/Backoffice/AlertsApiController.cs b/src/Enmarcha.Backend/Controllers/Backoffice/AlertsApiController.cs new file mode 100644 index 0000000..fe425d4 --- /dev/null +++ b/src/Enmarcha.Backend/Controllers/Backoffice/AlertsApiController.cs @@ -0,0 +1,14 @@ +using Enmarcha.Backend.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Enmarcha.Backend.Controllers.Backoffice; + +[Route("backoffice/api")] +[Authorize(AuthenticationSchemes = "Backoffice")] +public class AlertsApiController(BackofficeSelectorService selectors) : ControllerBase +{ + [HttpGet("selectors/transit")] + public async Task GetTransit() => + Ok(await selectors.GetTransitDataAsync()); +} diff --git a/src/Enmarcha.Backend/Controllers/Backoffice/AlertsController.cs b/src/Enmarcha.Backend/Controllers/Backoffice/AlertsController.cs new file mode 100644 index 0000000..4e83abc --- /dev/null +++ b/src/Enmarcha.Backend/Controllers/Backoffice/AlertsController.cs @@ -0,0 +1,83 @@ +using Enmarcha.Backend.Data; +using Enmarcha.Backend.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Enmarcha.Backend.Controllers.Backoffice; + +[Route("backoffice/alerts")] +[Authorize(AuthenticationSchemes = "Backoffice")] +public class AlertsController(AppDbContext db) : Controller +{ + [HttpGet("")] + public async Task Index() + { + var alerts = await db.ServiceAlerts + .OrderByDescending(a => a.InsertedDate) + .ToListAsync(); + return View(alerts); + } + + [HttpGet("create")] + public IActionResult Create() => View("Edit", new AlertFormViewModel()); + + [HttpPost("create")] + [ValidateAntiForgeryToken] + public async Task CreatePost(AlertFormViewModel model) + { + if (!ModelState.IsValid) + { + return View("Edit", model); + } + + db.ServiceAlerts.Add(model.ToServiceAlert()); + await db.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + + [HttpGet("{id}/edit")] + public async Task Edit(string id) + { + var alert = await db.ServiceAlerts.FindAsync(id); + if (alert is null) return NotFound(); + return View(AlertFormViewModel.FromServiceAlert(alert)); + } + + [HttpPost("{id}/edit")] + [ValidateAntiForgeryToken] + public async Task EditPost(string id, AlertFormViewModel model) + { + if (!ModelState.IsValid) + { + return View("Edit", model); + } + + var alert = await db.ServiceAlerts.FindAsync(id); + if (alert is null) return NotFound(); + + model.ApplyTo(alert); + await db.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + + [HttpGet("{id}/delete")] + public async Task Delete(string id) + { + var alert = await db.ServiceAlerts.FindAsync(id); + if (alert is null) return NotFound(); + return View(alert); + } + + [HttpPost("{id}/delete")] + [ValidateAntiForgeryToken] + public async Task DeleteConfirm(string id) + { + var alert = await db.ServiceAlerts.FindAsync(id); + if (alert is null) return NotFound(); + + db.ServiceAlerts.Remove(alert); + await db.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } +} diff --git a/src/Enmarcha.Backend/Controllers/Backoffice/BackofficeController.cs b/src/Enmarcha.Backend/Controllers/Backoffice/BackofficeController.cs new file mode 100644 index 0000000..a3c41dc --- /dev/null +++ b/src/Enmarcha.Backend/Controllers/Backoffice/BackofficeController.cs @@ -0,0 +1,18 @@ +using Enmarcha.Backend.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Enmarcha.Backend.Controllers.Backoffice; + +[Route("backoffice")] +[Authorize(AuthenticationSchemes = "Backoffice")] +public class BackofficeController(AppDbContext db) : Controller +{ + [HttpGet("")] + public async Task Index() + { + ViewData["AlertCount"] = await db.ServiceAlerts.CountAsync(); + return View(); + } +} diff --git a/src/Enmarcha.Backend/Controllers/Backoffice/LoginController.cs b/src/Enmarcha.Backend/Controllers/Backoffice/LoginController.cs new file mode 100644 index 0000000..1e9f12f --- /dev/null +++ b/src/Enmarcha.Backend/Controllers/Backoffice/LoginController.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Enmarcha.Backend.Controllers.Backoffice; + +[Route("backoffice/auth")] +public class LoginController : Controller +{ + [HttpGet("login")] + [AllowAnonymous] + public IActionResult Login(string returnUrl = "/backoffice") + { + return Challenge(new AuthenticationProperties { RedirectUri = returnUrl }, "Auth0"); + } + + [HttpPost("logout")] + [ValidateAntiForgeryToken] + [Authorize(AuthenticationSchemes = "Backoffice")] + public IActionResult Logout() + { + return SignOut( + new AuthenticationProperties { RedirectUri = "/backoffice" }, + "Backoffice", + "Auth0"); + } +} + diff --git a/src/Enmarcha.Backend/Controllers/TileController.cs b/src/Enmarcha.Backend/Controllers/TileController.cs index bf89a08..63e8a9a 100644 --- a/src/Enmarcha.Backend/Controllers/TileController.cs +++ b/src/Enmarcha.Backend/Controllers/TileController.cs @@ -72,7 +72,7 @@ public class TileController : ControllerBase var latMinRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * (y + 1) / n))); var latMin = latMinRad * 180.0 / Math.PI; - var requestContent = StopTileRequestContent.Query(new StopTileRequestContent.Bbox(lonMin, latMin, lonMax, latMax)); + var requestContent = StopTileRequestContent.Query(new StopTileRequestContent.TileRequestParams(lonMin, latMin, lonMax, latMax)); var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1"); request.Content = JsonContent.Create(new GraphClientRequest { -- cgit v1.3