Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions/Functions/UpdateSessionReportNoteFunction.cs
T
Oliver Kieselbach 14a3ce5b9c feat: fix session report download + add admin note feature
- Fix 500 error on download URL: replace GenerateSasUri() with
  blobClient.Uri (SAS already embedded in service-level connection string)
- Add AdminNote field to SessionReportMetadata shared model
- Read AdminNote from Table Storage in GetAllReportsAsync
- Add UpdateAdminNoteAsync to SessionReportService
- New PATCH /api/galactic/session-reports/{reportId}/note endpoint
- Frontend: editable Admin Note textarea in report detail modal with
  inline save feedback; Admin Note column in reports table with icon
- Add copy-to-clipboard buttons for Session ID, Tenant ID,
  Submitted By, and Email fields in report detail modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:37:52 +01:00

100 lines
4.3 KiB
C#

using System.Net;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AutopilotMonitor.Functions.Functions
{
/// <summary>
/// Allows Galactic Admins to add or update an admin note on a submitted session report.
/// </summary>
public class UpdateSessionReportNoteFunction
{
private readonly ILogger<UpdateSessionReportNoteFunction> _logger;
private readonly SessionReportService _sessionReportService;
private readonly GalacticAdminService _galacticAdminService;
public UpdateSessionReportNoteFunction(
ILogger<UpdateSessionReportNoteFunction> logger,
SessionReportService sessionReportService,
GalacticAdminService galacticAdminService)
{
_logger = logger;
_sessionReportService = sessionReportService;
_galacticAdminService = galacticAdminService;
}
[Function("UpdateSessionReportNote")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "galactic/session-reports/{reportId}/note")] HttpRequestData req,
string reportId)
{
try
{
if (!TenantHelper.IsAuthenticated(req))
{
var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized);
await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." });
return unauthorizedResponse;
}
var userIdentifier = TenantHelper.GetUserIdentifier(req);
var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier);
if (!isGalacticAdmin)
{
var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden);
await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." });
return forbiddenResponse;
}
if (string.IsNullOrEmpty(reportId))
{
var badRequest = req.CreateResponse(HttpStatusCode.BadRequest);
await badRequest.WriteAsJsonAsync(new { success = false, message = "reportId is required." });
return badRequest;
}
string body = await req.ReadAsStringAsync() ?? string.Empty;
JObject json;
try
{
json = JObject.Parse(body);
}
catch (JsonException)
{
var badRequest = req.CreateResponse(HttpStatusCode.BadRequest);
await badRequest.WriteAsJsonAsync(new { success = false, message = "Invalid JSON body." });
return badRequest;
}
var adminNote = json["adminNote"]?.ToString() ?? string.Empty;
var updated = await _sessionReportService.UpdateAdminNoteAsync(reportId, adminNote);
if (!updated)
{
var notFoundResponse = req.CreateResponse(HttpStatusCode.NotFound);
await notFoundResponse.WriteAsJsonAsync(new { success = false, message = "Report not found." });
return notFoundResponse;
}
_logger.LogInformation("Admin note updated for report {ReportId} by {User}", reportId, userIdentifier);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new { success = true });
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating admin note for report {ReportId}", reportId);
var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
await errorResponse.WriteAsJsonAsync(new { success = false, message = "Internal server error." });
return errorResponse;
}
}
}
}