feat(tasks): persist task deadlines
This commit is contained in:
@@ -7,4 +7,5 @@ public class CreateTaskRequest
|
|||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
public int? ParentTaskId { get; set; }
|
public int? ParentTaskId { get; set; }
|
||||||
public int? EstimatedMinutes { get; set; }
|
public int? EstimatedMinutes { get; set; }
|
||||||
|
public DateTimeOffset? DueAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ public class UpdateTaskRequest
|
|||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
public int? EstimatedMinutes { get; set; }
|
public int? EstimatedMinutes { get; set; }
|
||||||
public int? ParentTaskId { get; set; }
|
public int? ParentTaskId { get; set; }
|
||||||
|
public DateTimeOffset? DueAt { get; set; }
|
||||||
|
public bool ClearDueAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class WorkTask
|
|||||||
public DateTime? StartedAt { get; set; }
|
public DateTime? StartedAt { get; set; }
|
||||||
public DateTime? CompletedAt { get; set; }
|
public DateTime? CompletedAt { get; set; }
|
||||||
public int? EstimatedMinutes { get; set; }
|
public int? EstimatedMinutes { get; set; }
|
||||||
|
public DateTimeOffset? DueAt { get; set; }
|
||||||
|
|
||||||
public int? ParentTaskId { get; set; }
|
public int? ParentTaskId { get; set; }
|
||||||
public WorkTask? ParentTask { get; set; }
|
public WorkTask? ParentTask { get; set; }
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ public class TaskTrackerDbContext(DbContextOptions<TaskTrackerDbContext> options
|
|||||||
e.Property(t => t.Title).HasMaxLength(500).IsRequired();
|
e.Property(t => t.Title).HasMaxLength(500).IsRequired();
|
||||||
e.Property(t => t.Status).HasConversion<string>().HasMaxLength(50);
|
e.Property(t => t.Status).HasConversion<string>().HasMaxLength(50);
|
||||||
e.Property(t => t.Category).HasMaxLength(100);
|
e.Property(t => t.Category).HasMaxLength(100);
|
||||||
|
e.Property(t => t.DueAt).HasColumnType("datetimeoffset");
|
||||||
e.HasMany(t => t.SubTasks).WithOne(t => t.ParentTask).HasForeignKey(t => t.ParentTaskId).OnDelete(DeleteBehavior.Restrict);
|
e.HasMany(t => t.SubTasks).WithOne(t => t.ParentTask).HasForeignKey(t => t.ParentTaskId).OnDelete(DeleteBehavior.Restrict);
|
||||||
e.HasIndex(t => t.ParentTaskId);
|
e.HasIndex(t => t.ParentTaskId);
|
||||||
|
e.HasIndex(t => t.DueAt);
|
||||||
e.HasMany(t => t.Notes).WithOne(n => n.WorkTask).HasForeignKey(n => n.WorkTaskId);
|
e.HasMany(t => t.Notes).WithOne(n => n.WorkTask).HasForeignKey(n => n.WorkTaskId);
|
||||||
e.HasMany(t => t.ContextEvents).WithOne(c => c.WorkTask).HasForeignKey(c => c.WorkTaskId);
|
e.HasMany(t => t.ContextEvents).WithOne(c => c.WorkTask).HasForeignKey(c => c.WorkTaskId);
|
||||||
});
|
});
|
||||||
|
|||||||
+267
@@ -0,0 +1,267 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using TaskTracker.Infrastructure.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TaskTracker.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TaskTrackerDbContext))]
|
||||||
|
[Migration("20260813203936_AddTaskDueAt")]
|
||||||
|
partial class AddTaskDueAt
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.3")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.AppMapping", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("MatchType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Pattern")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("AppMappings");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Category = "Engineering",
|
||||||
|
FriendlyName = "SolidWorks",
|
||||||
|
MatchType = "ProcessName",
|
||||||
|
Pattern = "SLDWORKS"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Category = "Email",
|
||||||
|
FriendlyName = "Outlook",
|
||||||
|
MatchType = "ProcessName",
|
||||||
|
Pattern = "OUTLOOK"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Category = "General",
|
||||||
|
FriendlyName = "Notepad",
|
||||||
|
MatchType = "ProcessName",
|
||||||
|
Pattern = "notepad"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
Category = "LaserCutting",
|
||||||
|
FriendlyName = "PEP System",
|
||||||
|
MatchType = "UrlContains",
|
||||||
|
Pattern = "pep"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
Category = "Engineering",
|
||||||
|
FriendlyName = "SolidWorks",
|
||||||
|
MatchType = "TitleContains",
|
||||||
|
Pattern = "solidworks"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.ContextEvent", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AppName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Source")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Timestamp")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
|
b.Property<string>("WindowTitle")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("nvarchar(1000)");
|
||||||
|
|
||||||
|
b.Property<int?>("WorkTaskId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Timestamp");
|
||||||
|
|
||||||
|
b.HasIndex("WorkTaskId");
|
||||||
|
|
||||||
|
b.ToTable("ContextEvents");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.TaskNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<int>("WorkTaskId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("WorkTaskId");
|
||||||
|
|
||||||
|
b.ToTable("Notes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("CompletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DueAt")
|
||||||
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
|
b.Property<int?>("EstimatedMinutes")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("ParentTaskId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DueAt");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId");
|
||||||
|
|
||||||
|
b.ToTable("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.ContextEvent", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TaskTracker.Core.Entities.WorkTask", "WorkTask")
|
||||||
|
.WithMany("ContextEvents")
|
||||||
|
.HasForeignKey("WorkTaskId");
|
||||||
|
|
||||||
|
b.Navigation("WorkTask");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.TaskNote", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TaskTracker.Core.Entities.WorkTask", "WorkTask")
|
||||||
|
.WithMany("Notes")
|
||||||
|
.HasForeignKey("WorkTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("WorkTask");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TaskTracker.Core.Entities.WorkTask", "ParentTask")
|
||||||
|
.WithMany("SubTasks")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("ParentTask");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TaskTracker.Core.Entities.WorkTask", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ContextEvents");
|
||||||
|
|
||||||
|
b.Navigation("Notes");
|
||||||
|
|
||||||
|
b.Navigation("SubTasks");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TaskTracker.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddTaskDueAt : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||||
|
name: "DueAt",
|
||||||
|
table: "Tasks",
|
||||||
|
type: "datetimeoffset",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Tasks_DueAt",
|
||||||
|
table: "Tasks",
|
||||||
|
column: "DueAt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Tasks_DueAt",
|
||||||
|
table: "Tasks");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "DueAt",
|
||||||
|
table: "Tasks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -189,6 +189,9 @@ namespace TaskTracker.Infrastructure.Migrations
|
|||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DueAt")
|
||||||
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<int?>("EstimatedMinutes")
|
b.Property<int?>("EstimatedMinutes")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -210,6 +213,8 @@ namespace TaskTracker.Infrastructure.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DueAt");
|
||||||
|
|
||||||
b.HasIndex("ParentTaskId");
|
b.HasIndex("ParentTaskId");
|
||||||
|
|
||||||
b.ToTable("Tasks");
|
b.ToTable("Tasks");
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TaskTracker.Core\TaskTracker.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using TaskTracker.Core.Entities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace TaskTracker.Tests;
|
||||||
|
|
||||||
|
public class WorkTaskDeadlineTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void WorkTask_preserves_an_explicit_deadline_offset()
|
||||||
|
{
|
||||||
|
var dueAt = DateTimeOffset.Parse("2026-08-14T11:00:00-04:00");
|
||||||
|
var task = new WorkTask { DueAt = dueAt };
|
||||||
|
|
||||||
|
Assert.Equal(dueAt, task.DueAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
<Project Path="TaskTracker.Core/TaskTracker.Core.csproj" />
|
<Project Path="TaskTracker.Core/TaskTracker.Core.csproj" />
|
||||||
<Project Path="TaskTracker.Infrastructure/TaskTracker.Infrastructure.csproj" />
|
<Project Path="TaskTracker.Infrastructure/TaskTracker.Infrastructure.csproj" />
|
||||||
<Project Path="TaskTracker.MCP/TaskTracker.MCP.csproj" />
|
<Project Path="TaskTracker.MCP/TaskTracker.MCP.csproj" />
|
||||||
|
<Project Path="TaskTracker.Tests/TaskTracker.Tests.csproj" />
|
||||||
<Project Path="WindowWatcher/WindowWatcher.csproj" />
|
<Project Path="WindowWatcher/WindowWatcher.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
Reference in New Issue
Block a user