using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services.Push; using AutopilotMonitor.Shared.DataAccess; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace AutopilotMonitor.Functions.Functions.Push; /// /// "Notify me when this session ends" on the session page (TenantAdminOrOperator, QueryParam /// scoping like the annotations route so a Global Admin can watch a customer's session from /// their own paired devices — the devices stay in the caller's own scope). /// public class SessionWatchFunctions { private readonly PushSessionWatchService _watches; private readonly ISessionRepository _sessionRepo; private readonly ILogger _logger; public SessionWatchFunctions(PushSessionWatchService watches, ISessionRepository sessionRepo, ILogger logger) { _watches = watches; _sessionRepo = sessionRepo; _logger = logger; } [Function("GetSessionWatch")] public async Task Get( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "sessions/{sessionId}/watch")] HttpRequestData req, string sessionId) { try { var (tenantId, caller) = await ScopeAsync(req, sessionId); return await req.OkAsync(await _watches.GetAsync(tenantId, sessionId, caller)); } catch (Exception ex) { return await req.InternalServerErrorAsync(_logger, ex, "GetSessionWatch"); } } [Function("WatchSession")] public async Task Watch( [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "sessions/{sessionId}/watch")] HttpRequestData req, string sessionId) { try { if (!PushFunctionSupport.IsPlausibleId(sessionId)) return await req.NotFoundAsync("Session not found."); var (tenantId, caller) = await ScopeAsync(req, sessionId); if (await _sessionRepo.GetSessionAsync(tenantId, sessionId) == null) return await req.NotFoundAsync("Session not found."); var result = await _watches.WatchAsync(tenantId, sessionId, caller); return result.Ok ? await req.OkAsync(result.Value!) : await PushFunctionSupport.ErrorAsync(req, result.Error, result.Message); } catch (Exception ex) { return await req.InternalServerErrorAsync(_logger, ex, "WatchSession"); } } [Function("UnwatchSession")] public async Task Unwatch( [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "sessions/{sessionId}/watch")] HttpRequestData req, string sessionId) { try { var (tenantId, caller) = await ScopeAsync(req, sessionId); return await req.OkAsync(await _watches.UnwatchAsync(tenantId, sessionId, caller)); } catch (Exception ex) { return await req.InternalServerErrorAsync(_logger, ex, "UnwatchSession"); } } /// /// The watch lives in the SESSION's tenant; the devices it fires on are the caller's own /// (home-tenant scope, plus the platform scope for a Global Admin), resolved at terminal status /// by owner oid. /// private async Task<(string TenantId, PushCaller Caller)> ScopeAsync(HttpRequestData req, string sessionId) { var ctx = req.GetRequestContext(); var tenantId = await ctx.ResolveSessionScopeAsync(_sessionRepo, sessionId, requireGlobalAdmin: true); var caller = new PushCaller(ctx.UserPrincipalName, ctx.ObjectId, ctx.TenantId, IsScopeAdmin: ctx.IsTenantAdmin || ctx.IsGlobalAdmin, IsGlobalAdmin: ctx.IsGlobalAdmin); return (tenantId, caller); } }