Files
TaskTracker/TaskTracker.Api/Controllers/ContextController.cs
AJ Isaacs e12f78c479 chore: initial commit of TaskTracker project
Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:08:45 -05:00

84 lines
2.9 KiB
C#

using Microsoft.AspNetCore.Mvc;
using TaskTracker.Core.DTOs;
using TaskTracker.Core.Entities;
using TaskTracker.Core.Interfaces;
namespace TaskTracker.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ContextController(
IContextEventRepository contextRepo,
IAppMappingRepository mappingRepo,
ITaskRepository taskRepo,
ILogger<ContextController> logger) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Ingest([FromBody] ContextEventRequest request)
{
// Try to auto-link to the active task
var activeTask = await taskRepo.GetActiveTaskAsync();
var contextEvent = new ContextEvent
{
Source = request.Source,
AppName = request.AppName,
WindowTitle = request.WindowTitle,
Url = request.Url,
WorkTaskId = activeTask?.Id
};
// Try to find a matching app mapping for category enrichment
var mapping = await mappingRepo.FindMatchAsync(request.AppName, request.WindowTitle, request.Url);
if (mapping is not null)
{
logger.LogDebug("Matched context event to category {Category} via {MatchType}",
mapping.Category, mapping.MatchType);
}
var created = await contextRepo.CreateAsync(contextEvent);
return Ok(ApiResponse<ContextEvent>.Ok(created));
}
[HttpGet("recent")]
public async Task<IActionResult> GetRecent([FromQuery] int minutes = 30)
{
var events = await contextRepo.GetRecentAsync(minutes);
return Ok(ApiResponse<List<ContextEvent>>.Ok(events));
}
[HttpGet("summary")]
public async Task<IActionResult> GetSummary()
{
// Get today's events (last 8 hours)
var events = await contextRepo.GetRecentAsync(480);
var mappings = await mappingRepo.GetAllAsync();
var summary = events
.GroupBy(e => e.AppName)
.Select(g =>
{
var match = mappings.FirstOrDefault(m => m.MatchType switch
{
"ProcessName" => g.Key.Contains(m.Pattern, StringComparison.OrdinalIgnoreCase),
"TitleContains" => g.Any(e => e.WindowTitle.Contains(m.Pattern, StringComparison.OrdinalIgnoreCase)),
"UrlContains" => g.Any(e => e.Url?.Contains(m.Pattern, StringComparison.OrdinalIgnoreCase) == true),
_ => false
});
return new ContextSummaryItem
{
AppName = match?.FriendlyName ?? g.Key,
Category = match?.Category ?? "Unknown",
EventCount = g.Count(),
FirstSeen = g.Min(e => e.Timestamp),
LastSeen = g.Max(e => e.Timestamp)
};
})
.OrderByDescending(s => s.EventCount)
.ToList();
return Ok(ApiResponse<List<ContextSummaryItem>>.Ok(summary));
}
}