feat(prime): carry the action kind through the hub and validate custom prompts
This commit is contained in:
@@ -1033,11 +1033,14 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
using var ctx = _dbFactory.CreateDbContext();
|
using var ctx = _dbFactory.CreateDbContext();
|
||||||
var rows = await new PrimeScheduleRepository(ctx).ListAsync();
|
var rows = await new PrimeScheduleRepository(ctx).ListAsync();
|
||||||
return rows.Select(e => new PrimeScheduleDto(
|
return rows.Select(e => new PrimeScheduleDto(
|
||||||
e.Id, (int)e.Days, e.TimeOfDay, e.Enabled, e.LastRunAt, e.PromptOverride)).ToList();
|
e.Id, (int)e.Days, e.TimeOfDay, e.Enabled, e.LastRunAt, e.PromptOverride, e.Kind)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PrimeScheduleDto> UpsertPrimeSchedule(PrimeScheduleDto dto)
|
public async Task<PrimeScheduleDto> UpsertPrimeSchedule(PrimeScheduleDto dto)
|
||||||
{
|
{
|
||||||
|
var error = PrimeScheduleValidation.Validate(dto);
|
||||||
|
if (error is not null) throw new HubException(error);
|
||||||
|
|
||||||
using var ctx = _dbFactory.CreateDbContext();
|
using var ctx = _dbFactory.CreateDbContext();
|
||||||
var repo = new PrimeScheduleRepository(ctx);
|
var repo = new PrimeScheduleRepository(ctx);
|
||||||
var existing = await repo.GetAsync(dto.Id);
|
var existing = await repo.GetAsync(dto.Id);
|
||||||
@@ -1048,13 +1051,14 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
TimeOfDay = dto.TimeOfDay,
|
TimeOfDay = dto.TimeOfDay,
|
||||||
Enabled = dto.Enabled,
|
Enabled = dto.Enabled,
|
||||||
PromptOverride = dto.PromptOverride,
|
PromptOverride = dto.PromptOverride,
|
||||||
|
Kind = dto.Kind,
|
||||||
CreatedAt = existing?.CreatedAt ?? DateTimeOffset.UtcNow,
|
CreatedAt = existing?.CreatedAt ?? DateTimeOffset.UtcNow,
|
||||||
LastRunAt = existing?.LastRunAt,
|
LastRunAt = existing?.LastRunAt,
|
||||||
};
|
};
|
||||||
await repo.UpsertAsync(entity);
|
await repo.UpsertAsync(entity);
|
||||||
_primeSignal.Signal();
|
_primeSignal.Signal();
|
||||||
return new PrimeScheduleDto(entity.Id, (int)entity.Days, entity.TimeOfDay,
|
return new PrimeScheduleDto(entity.Id, (int)entity.Days, entity.TimeOfDay,
|
||||||
entity.Enabled, entity.LastRunAt, entity.PromptOverride);
|
entity.Enabled, entity.LastRunAt, entity.PromptOverride, entity.Kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeletePrimeSchedule(Guid id)
|
public async Task DeletePrimeSchedule(Guid id)
|
||||||
@@ -1072,7 +1076,9 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
|
|
||||||
public async Task<bool> RunDailyPrepNow()
|
public async Task<bool> RunDailyPrepNow()
|
||||||
{
|
{
|
||||||
var schedule = new PrimeScheduleDto(Guid.Empty, 0, TimeSpan.Zero, true, null, null);
|
var schedule = new PrimeScheduleDto(
|
||||||
|
Guid.Empty, 0, TimeSpan.Zero, true, null, null,
|
||||||
|
ClaudeDo.Data.Models.PrimeActionKind.FillMyDay);
|
||||||
var firedAt = DateTimeOffset.Now;
|
var firedAt = DateTimeOffset.Now;
|
||||||
var outcome = await _primeRunner.FireAsync(schedule, Context.ConnectionAborted);
|
var outcome = await _primeRunner.FireAsync(schedule, Context.ConnectionAborted);
|
||||||
await _broadcaster.PrimeFired(Guid.Empty, outcome.Success, outcome.Message, firedAt);
|
await _broadcaster.PrimeFired(Guid.Empty, outcome.Success, outcome.Message, firedAt);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public static class PrimeScheduleValidation
|
||||||
|
{
|
||||||
|
/// <summary>Returns an error message, or null when the schedule is safe to persist.</summary>
|
||||||
|
public static string? Validate(PrimeScheduleDto dto)
|
||||||
|
{
|
||||||
|
if (dto.Kind == PrimeActionKind.Custom && string.IsNullOrWhiteSpace(dto.PromptOverride))
|
||||||
|
return "A custom Prime schedule needs a prompt.";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ public class NextDueCalculatorTests
|
|||||||
private static PrimeScheduleDto Schedule(
|
private static PrimeScheduleDto Schedule(
|
||||||
PrimeDays days, TimeSpan time,
|
PrimeDays days, TimeSpan time,
|
||||||
bool enabled = true, DateTimeOffset? lastRun = null) =>
|
bool enabled = true, DateTimeOffset? lastRun = null) =>
|
||||||
new(Guid.NewGuid(), (int)days, time, enabled, lastRun, null);
|
new(Guid.NewGuid(), (int)days, time, enabled, lastRun, null, PrimeActionKind.FillMyDay);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Disabled_Schedule_Returns_Null()
|
public void Disabled_Schedule_Returns_Null()
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Prime;
|
||||||
|
|
||||||
|
public class PrimeScheduleValidationTests
|
||||||
|
{
|
||||||
|
private static PrimeScheduleDto Dto(PrimeActionKind kind, string? prompt) =>
|
||||||
|
new(Guid.NewGuid(), 31, new TimeSpan(7, 0, 0), true, null, prompt, kind);
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null)]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(" ")]
|
||||||
|
public void Custom_without_a_prompt_is_rejected(string? prompt)
|
||||||
|
{
|
||||||
|
var error = PrimeScheduleValidation.Validate(Dto(PrimeActionKind.Custom, prompt));
|
||||||
|
Assert.NotNull(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Custom_with_a_prompt_is_accepted()
|
||||||
|
{
|
||||||
|
Assert.Null(PrimeScheduleValidation.Validate(Dto(PrimeActionKind.Custom, "do the thing")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ping_without_a_prompt_is_accepted()
|
||||||
|
{
|
||||||
|
Assert.Null(PrimeScheduleValidation.Validate(Dto(PrimeActionKind.Ping, null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FillMyDay_without_a_prompt_is_accepted()
|
||||||
|
{
|
||||||
|
Assert.Null(PrimeScheduleValidation.Validate(Dto(PrimeActionKind.FillMyDay, null)));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user