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>
This commit is contained in:
83
TaskTracker.Api/Controllers/ContextController.cs
Normal file
83
TaskTracker.Api/Controllers/ContextController.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user