Migrate IngestEvents, RegisterSession, DeleteSession, services to DAL

- IngestEventsFunction → ISessionRepository + IMetricsRepository + IMaintenanceRepository
- RegisterSessionFunction → ISessionRepository + IMetricsRepository
- DeleteSessionFunction → ISessionRepository + IMaintenanceRepository
- SubmitSessionReportFunction → IMaintenanceRepository
- RuleEngine → IRuleRepository
- UsageMetricsService → IMetricsRepository + IMaintenanceRepository + ISessionRepository
- PlatformMetricsService → IMetricsRepository + IMaintenanceRepository
- Add StoreEventsBatchAsync to ISessionRepository + TableSessionRepository

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
This commit is contained in:
Claude
2026-03-27 06:32:10 +00:00
parent 2c83bca248
commit 7bf139ecbe
12 changed files with 85 additions and 45 deletions
@@ -78,6 +78,14 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
return result;
}
public async Task<List<EnrollmentEvent>> StoreEventsBatchAsync(List<EnrollmentEvent> events)
{
var result = await _storage.StoreEventsBatchAsync(events);
if (result.Count > 0 && events.Count > 0)
await _publisher.PublishAsync("events.ingested", new { count = result.Count }, events[0].TenantId);
return result;
}
public Task<List<EnrollmentEvent>> GetSessionEventsAsync(string tenantId, string sessionId, int maxResults = 1000)
=> _storage.GetSessionEventsAsync(tenantId, sessionId, maxResults);
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Collections.Generic;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
@@ -4,6 +4,7 @@ using AutopilotMonitor.Functions.Security;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Functions.Services.Notifications;
using AutopilotMonitor.Functions.Services.Vulnerability;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using AutopilotMonitor.Shared.Models.Notifications;
using Microsoft.Azure.Functions.Worker.Http;
@@ -128,7 +129,7 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
StampServerFields(request.Events, request.TenantId, request.SessionId, receivedAt);
// Store events in Azure Table Storage (batch write for efficiency)
var storedEvents = await _storageService.StoreEventsBatchAsync(request.Events);
var storedEvents = await _sessionRepo.StoreEventsBatchAsync(request.Events);
int processedCount = storedEvents.Count;
// Update EventTypeIndex and DeviceSnapshot indexes (fire-and-forget, non-blocking)
@@ -136,10 +137,10 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
var indexSessionId = request.SessionId;
var indexEvents = storedEvents.ToList();
_ = Task.WhenAll(
_storageService.UpsertEventTypeIndexBatchAsync(
_sessionRepo.UpsertEventTypeIndexBatchAsync(
indexTenantId, indexSessionId,
indexEvents.Select(e => e.EventType).Distinct()),
_storageService.UpsertDeviceSnapshotAsync(
_sessionRepo.UpsertDeviceSnapshotAsync(
indexTenantId, indexSessionId, indexEvents)
).ContinueWith(t => _logger.LogWarning(t.Exception?.InnerException,
"Index update failed (non-fatal)"), TaskContinuationOptions.OnlyOnFaulted);
@@ -152,7 +153,7 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
var imeVersion = imeVersionEvent.Data["agentVersion"]?.ToString();
if (!string.IsNullOrEmpty(imeVersion))
{
_ = _storageService.UpdateSessionImeAgentVersionAsync(request.TenantId, request.SessionId, imeVersion)
_ = _sessionRepo.UpdateSessionImeAgentVersionAsync(request.TenantId, request.SessionId, imeVersion)
.ContinueWith(t => _logger.LogWarning(t.Exception?.InnerException,
"ImeAgentVersion update failed (non-fatal)"), TaskContinuationOptions.OnlyOnFaulted);
}
@@ -164,7 +165,7 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
// Store app install summaries
foreach (var summary in classification.AppInstallUpdates.Values)
{
await _storageService.StoreAppInstallSummaryAsync(summary.Summary);
await _metricsRepo.StoreAppInstallSummaryAsync(summary.Summary);
}
// Extract geo-location data and merge into session row (fire-and-forget —
@@ -3,6 +3,7 @@ using AutopilotMonitor.Functions.Security;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Functions.Services.Notifications;
using AutopilotMonitor.Functions.Services.Vulnerability;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
@@ -14,7 +15,11 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
public partial class IngestEventsFunction
{
private readonly ILogger<IngestEventsFunction> _logger;
private readonly TableStorageService _storageService;
private readonly ISessionRepository _sessionRepo;
private readonly IMetricsRepository _metricsRepo;
private readonly IMaintenanceRepository _maintenanceRepo;
private readonly IRuleRepository _ruleRepo;
private readonly IVulnerabilityRepository _vulnRepo;
private readonly TenantConfigurationService _configService;
private readonly RateLimitService _rateLimitService;
private readonly AutopilotDeviceValidator _autopilotDeviceValidator;
@@ -30,7 +35,11 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
public IngestEventsFunction(
ILogger<IngestEventsFunction> logger,
TableStorageService storageService,
ISessionRepository sessionRepo,
IMetricsRepository metricsRepo,
IMaintenanceRepository maintenanceRepo,
IRuleRepository ruleRepo,
IVulnerabilityRepository vulnRepo,
TenantConfigurationService configService,
RateLimitService rateLimitService,
AutopilotDeviceValidator autopilotDeviceValidator,
@@ -45,7 +54,11 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
SignalRNotificationService signalRNotification)
{
_logger = logger;
_storageService = storageService;
_sessionRepo = sessionRepo;
_metricsRepo = metricsRepo;
_maintenanceRepo = maintenanceRepo;
_ruleRepo = ruleRepo;
_vulnRepo = vulnRepo;
_configService = configService;
_rateLimitService = rateLimitService;
_autopilotDeviceValidator = autopilotDeviceValidator;
@@ -1,6 +1,7 @@
using System.Net;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
@@ -12,7 +13,7 @@ namespace AutopilotMonitor.Functions.Functions.Reports
{
private readonly ILogger<SubmitSessionReportFunction> _logger;
private readonly SessionReportService _sessionReportService;
private readonly TableStorageService _storageService;
private readonly IMaintenanceRepository _maintenanceRepo;
private readonly GlobalAdminService _globalAdminService;
private readonly TelegramNotificationService _telegramNotificationService;
private readonly GlobalNotificationService _globalNotificationService;
@@ -20,14 +21,14 @@ namespace AutopilotMonitor.Functions.Functions.Reports
public SubmitSessionReportFunction(
ILogger<SubmitSessionReportFunction> logger,
SessionReportService sessionReportService,
TableStorageService storageService,
IMaintenanceRepository maintenanceRepo,
GlobalAdminService globalAdminService,
TelegramNotificationService telegramNotificationService,
GlobalNotificationService globalNotificationService)
{
_logger = logger;
_sessionReportService = sessionReportService;
_storageService = storageService;
_maintenanceRepo = maintenanceRepo;
_globalAdminService = globalAdminService;
_telegramNotificationService = telegramNotificationService;
_globalNotificationService = globalNotificationService;
@@ -85,7 +86,7 @@ namespace AutopilotMonitor.Functions.Functions.Reports
// Log audit entry — skip for Global Admins
if (!isGlobalAdmin)
{
await _storageService.LogAuditEntryAsync(
await _maintenanceRepo.LogAuditEntryAsync(
request.TenantId,
"CREATE",
"SessionReport",
@@ -1,6 +1,6 @@
using System.Net;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared.DataAccess;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
@@ -10,14 +10,17 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
public class DeleteSessionFunction
{
private readonly ILogger<DeleteSessionFunction> _logger;
private readonly TableStorageService _storageService;
private readonly ISessionRepository _sessionRepo;
private readonly IMaintenanceRepository _maintenanceRepo;
public DeleteSessionFunction(
ILogger<DeleteSessionFunction> logger,
TableStorageService storageService)
ISessionRepository sessionRepo,
IMaintenanceRepository maintenanceRepo)
{
_logger = logger;
_storageService = storageService;
_sessionRepo = sessionRepo;
_maintenanceRepo = maintenanceRepo;
}
[Function("DeleteSession")]
@@ -36,22 +39,22 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
_logger.LogInformation($"Deleting session {sessionId} for tenant {tenantId} by user {userIdentifier}");
// Delete all related data for this session
var eventsDeleted = await _storageService.DeleteSessionEventsAsync(tenantId, sessionId);
var eventsDeleted = await _maintenanceRepo.DeleteSessionEventsAsync(tenantId, sessionId);
_logger.LogInformation($"Deleted {eventsDeleted} events for session {sessionId}");
var ruleResultsDeleted = await _storageService.DeleteSessionRuleResultsAsync(tenantId, sessionId);
var ruleResultsDeleted = await _maintenanceRepo.DeleteSessionRuleResultsAsync(tenantId, sessionId);
_logger.LogInformation($"Deleted {ruleResultsDeleted} rule results for session {sessionId}");
var appSummariesDeleted = await _storageService.DeleteSessionAppInstallSummariesAsync(tenantId, sessionId);
var appSummariesDeleted = await _maintenanceRepo.DeleteSessionAppInstallSummariesAsync(tenantId, sessionId);
_logger.LogInformation($"Deleted {appSummariesDeleted} app install summaries for session {sessionId}");
// Delete the session itself
var sessionDeleted = await _storageService.DeleteSessionAsync(tenantId, sessionId);
var sessionDeleted = await _sessionRepo.DeleteSessionAsync(tenantId, sessionId);
if (sessionDeleted)
{
// Log audit entry with actual user identifier
await _storageService.LogAuditEntryAsync(
await _maintenanceRepo.LogAuditEntryAsync(
tenantId,
"DELETE",
"Session",
@@ -14,7 +14,8 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
public class RegisterSessionFunction
{
private readonly ILogger<RegisterSessionFunction> _logger;
private readonly TableStorageService _storageService;
private readonly ISessionRepository _sessionRepo;
private readonly IMetricsRepository _metricsRepo;
private readonly TenantConfigurationService _configService;
private readonly RateLimitService _rateLimitService;
private readonly AutopilotDeviceValidator _autopilotDeviceValidator;
@@ -23,7 +24,8 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
public RegisterSessionFunction(
ILogger<RegisterSessionFunction> logger,
TableStorageService storageService,
ISessionRepository sessionRepo,
IMetricsRepository metricsRepo,
TenantConfigurationService configService,
RateLimitService rateLimitService,
AutopilotDeviceValidator autopilotDeviceValidator,
@@ -31,7 +33,8 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
BootstrapSessionService bootstrapSessionService)
{
_logger = logger;
_storageService = storageService;
_sessionRepo = sessionRepo;
_metricsRepo = metricsRepo;
_configService = configService;
_rateLimitService = rateLimitService;
_autopilotDeviceValidator = autopilotDeviceValidator;
@@ -102,7 +105,7 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
_logger.LogInformation($"Registering session {registration.SessionId} for tenant {registration.TenantId} (Device: {validation.CertificateThumbprint})");
// Store session in Azure Table Storage
var stored = await _storageService.StoreSessionAsync(registration);
var stored = await _sessionRepo.StoreSessionAsync(registration);
if (!stored)
{
@@ -111,11 +114,11 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
}
// Increment platform stats (fire-and-forget, non-blocking)
_ = _storageService.IncrementPlatformStatAsync("TotalEnrollments")
_ = _metricsRepo.IncrementPlatformStatAsync("TotalEnrollments")
.ContinueWith(t => _logger.LogWarning(t.Exception?.InnerException, "Fire-and-forget IncrementPlatformStatAsync failed"), TaskContinuationOptions.OnlyOnFaulted);
// Retrieve the stored session to include full data in SignalR message
var session = await _storageService.GetSessionAsync(registration.TenantId, registration.SessionId);
var session = await _sessionRepo.GetSessionAsync(registration.TenantId, registration.SessionId);
// Check if admin has already marked this session as terminal (e.g., agent restarted after admin override)
string? adminAction = null;
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AutopilotMonitor.Shared.DataAccess;
using Microsoft.Extensions.Logging;
namespace AutopilotMonitor.Functions.Services
@@ -13,7 +14,7 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public class PlatformMetricsService
{
private readonly TableStorageService _storageService;
private readonly ISessionRepository _sessionRepo;
private readonly ILogger<PlatformMetricsService> _logger;
// In-memory cache (same pattern as UsageMetricsService)
@@ -23,10 +24,10 @@ namespace AutopilotMonitor.Functions.Services
private static readonly object _cacheLock = new object();
public PlatformMetricsService(
TableStorageService storageService,
ISessionRepository sessionRepo,
ILogger<PlatformMetricsService> logger)
{
_storageService = storageService;
_sessionRepo = sessionRepo;
_logger = logger;
}
@@ -73,7 +74,7 @@ namespace AutopilotMonitor.Functions.Services
private async Task<PlatformAgentMetricsResponse> ComputePlatformMetricsInternalAsync()
{
// 1. Fetch the 100 most recent sessions across all tenants
var page = await _storageService.GetAllSessionsAsync(maxResults: 100);
var page = await _sessionRepo.GetAllSessionsAsync(maxResults: 100);
var allSessions = page.Sessions;
if (allSessions.Count == 0)
@@ -88,7 +89,7 @@ namespace AutopilotMonitor.Functions.Services
{
try
{
var events = await _storageService.GetSessionEventsAsync(session.TenantId, session.SessionId);
var events = await _sessionRepo.GetSessionEventsAsync(session.TenantId, session.SessionId);
var snapshots = events
.Where(e => e.EventType == "agent_metrics_snapshot" && e.Data != null)
.Select(e => e.Data)
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Microsoft.Extensions.Logging;
@@ -12,13 +13,15 @@ namespace AutopilotMonitor.Functions.Services
public partial class RuleEngine
{
private readonly AnalyzeRuleService _ruleService;
private readonly TableStorageService _storageService;
private readonly IRuleRepository _ruleRepo;
private readonly ISessionRepository _sessionRepo;
private readonly ILogger _logger;
public RuleEngine(AnalyzeRuleService ruleService, TableStorageService storageService, ILogger logger)
public RuleEngine(AnalyzeRuleService ruleService, IRuleRepository ruleRepo, ISessionRepository sessionRepo, ILogger logger)
{
_ruleService = ruleService;
_storageService = storageService;
_ruleRepo = ruleRepo;
_sessionRepo = sessionRepo;
_logger = logger;
}
@@ -34,7 +37,7 @@ namespace AutopilotMonitor.Functions.Services
try
{
var activeRules = await _ruleService.GetActiveRulesForTenantAsync(tenantId);
var allEvents = await _storageService.GetSessionEventsAsync(tenantId, sessionId);
var allEvents = await _sessionRepo.GetSessionEventsAsync(tenantId, sessionId);
if (allEvents.Count == 0)
{
@@ -51,7 +54,7 @@ namespace AutopilotMonitor.Functions.Services
}
else
{
var existingResults = await _storageService.GetRuleResultsAsync(tenantId, sessionId);
var existingResults = await _ruleRepo.GetRuleResultsAsync(tenantId, sessionId);
existingRuleIds = new HashSet<string>(existingResults.Select(r => r.RuleId));
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AutopilotMonitor.Shared.DataAccess;
using AutopilotMonitor.Shared.Models;
using Microsoft.Extensions.Logging;
@@ -13,7 +14,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public class UsageMetricsService
{
private readonly TableStorageService _storageService;
private readonly IMetricsRepository _metricsRepo;
private readonly IMaintenanceRepository _maintenanceRepo;
private readonly ILogger<UsageMetricsService> _logger;
// In-memory cache
@@ -23,10 +25,12 @@ namespace AutopilotMonitor.Functions.Services
private static readonly object _cacheLock = new object();
public UsageMetricsService(
TableStorageService storageService,
IMetricsRepository metricsRepo,
IMaintenanceRepository maintenanceRepo,
ILogger<UsageMetricsService> logger)
{
_storageService = storageService;
_metricsRepo = metricsRepo;
_maintenanceRepo = maintenanceRepo;
_logger = logger;
}
@@ -95,7 +99,7 @@ namespace AutopilotMonitor.Functions.Services
// Query sessions from the last 90 days only — covers all displayed metrics (today/7d/30d)
// with a generous margin. Avoids loading the entire table into memory.
// "Total" counters use PlatformStats (cumulative, already tracked separately).
var allSessions = await _storageService.GetSessionsByDateRangeAsync(DateTime.UtcNow.AddDays(-90), DateTime.UtcNow.AddDays(1));
var allSessions = await _maintenanceRepo.GetSessionsByDateRangeAsync(DateTime.UtcNow.AddDays(-90), DateTime.UtcNow.AddDays(1));
var now = DateTime.UtcNow;
var today = now.Date;
@@ -132,7 +136,7 @@ namespace AutopilotMonitor.Functions.Services
};
// User Metrics (from UserActivity table)
var userActivity = await _storageService.GetAllUserActivityMetricsAsync();
var userActivity = await _metricsRepo.GetAllUserActivityMetricsAsync();
var userMetrics = new UserMetrics
{
Total = userActivity.TotalUniqueUsers,
@@ -196,7 +200,7 @@ namespace AutopilotMonitor.Functions.Services
};
// Platform Stats (cumulative since release)
var platformStats = await _storageService.GetPlatformStatsAsync();
var platformStats = await _metricsRepo.GetPlatformStatsAsync();
return new PlatformUsageMetrics
{
@@ -223,7 +227,7 @@ namespace AutopilotMonitor.Functions.Services
private async Task<PlatformUsageMetrics> ComputeTenantUsageMetricsInternalAsync(string tenantId)
{
// Query sessions for specific tenant only — last 90 days
var tenantSessions = await _storageService.GetSessionsByDateRangeAsync(DateTime.UtcNow.AddDays(-90), DateTime.UtcNow.AddDays(1), tenantId);
var tenantSessions = await _maintenanceRepo.GetSessionsByDateRangeAsync(DateTime.UtcNow.AddDays(-90), DateTime.UtcNow.AddDays(1), tenantId);
var now = DateTime.UtcNow;
var today = now.Date;
@@ -252,7 +256,7 @@ namespace AutopilotMonitor.Functions.Services
};
// User Metrics (from UserActivity table)
var userActivity = await _storageService.GetUserActivityMetricsAsync(tenantId);
var userActivity = await _metricsRepo.GetUserActivityMetricsAsync(tenantId);
var userMetrics = new UserMetrics
{
Total = userActivity.TotalUniqueUsers,
@@ -35,6 +35,7 @@ namespace AutopilotMonitor.Shared.DataAccess
// --- Events ---
Task<bool> StoreEventAsync(EnrollmentEvent evt);
Task<List<EnrollmentEvent>> StoreEventsBatchAsync(List<EnrollmentEvent> events);
Task<List<EnrollmentEvent>> GetSessionEventsAsync(string tenantId, string sessionId, int maxResults = 1000);
// --- Search ---