Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/Orbit.Api/Controllers/HabitsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Orbit.Application.Habits.Commands;
using Orbit.Application.Habits.Queries;
using Orbit.Domain.Enums;
using Orbit.Domain.ValueObjects;

namespace Orbit.Api.Controllers;

Expand All @@ -26,7 +27,8 @@ public record CreateHabitRequest(
bool ReminderEnabled = false,
int ReminderMinutesBefore = 15,
bool SlipAlertEnabled = false,
IReadOnlyList<Guid>? TagIds = null);
IReadOnlyList<Guid>? TagIds = null,
IReadOnlyList<ChecklistItem>? ChecklistItems = null);

public record UpdateHabitRequest(
string Title,
Expand All @@ -39,7 +41,10 @@ public record UpdateHabitRequest(
TimeOnly? DueTime = null,
bool? ReminderEnabled = null,
int? ReminderMinutesBefore = null,
bool? SlipAlertEnabled = null);
bool? SlipAlertEnabled = null,
IReadOnlyList<ChecklistItem>? ChecklistItems = null);

public record UpdateChecklistRequest(IReadOnlyList<ChecklistItem> ChecklistItems);

public record LogHabitRequest(string? Note = null);

Expand Down Expand Up @@ -139,7 +144,8 @@ public async Task<IActionResult> CreateHabit(
request.ReminderEnabled,
request.ReminderMinutesBefore,
request.SlipAlertEnabled,
request.TagIds);
request.TagIds,
request.ChecklistItems);

var result = await mediator.Send(command, cancellationToken);

Expand Down Expand Up @@ -189,7 +195,26 @@ public async Task<IActionResult> UpdateHabit(
request.DueTime,
request.ReminderEnabled,
request.ReminderMinutesBefore,
request.SlipAlertEnabled);
request.SlipAlertEnabled,
request.ChecklistItems);

var result = await mediator.Send(command, cancellationToken);

return result.IsSuccess
? NoContent()
: BadRequest(new { error = result.Error });
}

[HttpPut("{id:guid}/checklist")]
public async Task<IActionResult> UpdateChecklist(
Guid id,
[FromBody] UpdateChecklistRequest request,
CancellationToken cancellationToken)
{
var command = new UpdateChecklistCommand(
HttpContext.GetUserId(),
id,
request.ChecklistItems);

var result = await mediator.Send(command, cancellationToken);

Expand Down
7 changes: 5 additions & 2 deletions src/Orbit.Application/Chat/Commands/ProcessUserChatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Orbit.Domain.Enums;
using Orbit.Domain.Interfaces;
using Orbit.Domain.Models;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Chat.Commands;

Expand Down Expand Up @@ -386,7 +387,8 @@ public async Task<Result<ChatResponse>> Handle(
isBadHabit: isBadHabit,
dueDate: dueDate,
dueTime: action.DueTime,
slipAlertEnabled: slipAlertEnabled);
slipAlertEnabled: slipAlertEnabled,
checklistItems: action.ChecklistItems);

if (habitResult.IsFailure)
return Result.Failure<(Guid? Id, string? Name)>(habitResult.Error);
Expand Down Expand Up @@ -483,7 +485,8 @@ public async Task<Result<ChatResponse>> Handle(
action.Days,
action.IsBadHabit ?? habit.IsBadHabit,
action.DueDate ?? habit.DueDate,
dueTime: action.DueTime);
dueTime: action.DueTime,
checklistItems: action.ChecklistItems);

if (result.IsFailure)
return Result.Failure<(Guid? Id, string? Name)>(result.Error);
Expand Down
7 changes: 5 additions & 2 deletions src/Orbit.Application/Habits/Commands/CreateHabitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Orbit.Domain.Entities;
using Orbit.Domain.Enums;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Habits.Commands;

Expand All @@ -22,7 +23,8 @@ public record CreateHabitCommand(
bool ReminderEnabled = false,
int ReminderMinutesBefore = 15,
bool SlipAlertEnabled = false,
IReadOnlyList<Guid>? TagIds = null) : IRequest<Result<Guid>>;
IReadOnlyList<Guid>? TagIds = null,
IReadOnlyList<ChecklistItem>? ChecklistItems = null) : IRequest<Result<Guid>>;

public class CreateHabitCommandHandler(
IGenericRepository<Habit> habitRepository,
Expand Down Expand Up @@ -61,7 +63,8 @@ public async Task<Result<Guid>> Handle(CreateHabitCommand request, CancellationT
dueTime: request.DueTime,
reminderEnabled: request.ReminderEnabled,
reminderMinutesBefore: request.ReminderMinutesBefore,
slipAlertEnabled: request.SlipAlertEnabled);
slipAlertEnabled: request.SlipAlertEnabled,
checklistItems: request.ChecklistItems);

if (habitResult.IsFailure)
return Result.Failure<Guid>(habitResult.Error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private static Result<Habit> CloneHabit(Habit source, Guid? parentHabitId, bool
source.IsBadHabit,
source.DueDate,
source.DueTime,
parentHabitId);
parentHabitId,
checklistItems: source.ChecklistItems);
}
}
34 changes: 34 additions & 0 deletions src/Orbit.Application/Habits/Commands/UpdateChecklistCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using MediatR;
using Orbit.Application.Common;
using Orbit.Domain.Common;
using Orbit.Domain.Entities;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Habits.Commands;

public record UpdateChecklistCommand(
Guid UserId,
Guid HabitId,
IReadOnlyList<ChecklistItem> ChecklistItems) : IRequest<Result>;

public class UpdateChecklistCommandHandler(
IGenericRepository<Habit> habitRepository,
IUnitOfWork unitOfWork) : IRequestHandler<UpdateChecklistCommand, Result>
{
public async Task<Result> Handle(UpdateChecklistCommand request, CancellationToken cancellationToken)
{
var habit = await habitRepository.FindOneTrackedAsync(
h => h.Id == request.HabitId && h.UserId == request.UserId,
cancellationToken: cancellationToken);

if (habit is null)
return Result.Failure(ErrorMessages.HabitNotFound);

habit.UpdateChecklist(request.ChecklistItems);

await unitOfWork.SaveChangesAsync(cancellationToken);

return Result.Success();
}
}
7 changes: 5 additions & 2 deletions src/Orbit.Application/Habits/Commands/UpdateHabitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Orbit.Domain.Entities;
using Orbit.Domain.Enums;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;
using System.Linq.Expressions;

namespace Orbit.Application.Habits.Commands;
Expand All @@ -22,7 +23,8 @@ public record UpdateHabitCommand(
TimeOnly? DueTime = null,
bool? ReminderEnabled = null,
int? ReminderMinutesBefore = null,
bool? SlipAlertEnabled = null) : IRequest<Result>;
bool? SlipAlertEnabled = null,
IReadOnlyList<ChecklistItem>? ChecklistItems = null) : IRequest<Result>;

public class UpdateHabitCommandHandler(
IGenericRepository<Habit> habitRepository,
Expand Down Expand Up @@ -51,7 +53,8 @@ public async Task<Result> Handle(UpdateHabitCommand request, CancellationToken c
dueTime: request.DueTime,
reminderEnabled: request.ReminderEnabled,
reminderMinutesBefore: request.ReminderMinutesBefore,
slipAlertEnabled: request.SlipAlertEnabled);
slipAlertEnabled: request.SlipAlertEnabled,
checklistItems: request.ChecklistItems);

if (result.IsFailure)
return result;
Expand Down
5 changes: 4 additions & 1 deletion src/Orbit.Application/Habits/Queries/GetHabitByIdQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Orbit.Domain.Common;
using Orbit.Domain.Entities;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Habits.Queries;

Expand All @@ -18,6 +19,7 @@ public record HabitDetailResponse(
TimeOnly? DueTime,
IReadOnlyList<DayOfWeek> Days,
int? Position,
IReadOnlyList<ChecklistItem> ChecklistItems,
DateTime CreatedAtUtc,
IReadOnlyList<HabitChildResponse> Children);

Expand Down Expand Up @@ -56,6 +58,7 @@ public async Task<Result<HabitDetailResponse>> Handle(GetHabitByIdQuery request,
habit.DueTime,
habit.Days.ToList(),
habit.Position,
habit.ChecklistItems,
habit.CreatedAtUtc,
children));
}
Expand All @@ -64,7 +67,7 @@ public async Task<Result<HabitDetailResponse>> Handle(GetHabitByIdQuery request,
c.Id, c.Title, c.Description,
c.FrequencyUnit, c.FrequencyQuantity, c.IsBadHabit, c.IsCompleted,
c.Days.ToList(), c.DueDate, c.DueTime,
c.Position, MapChildren(c.Id, lookup));
c.Position, c.ChecklistItems, MapChildren(c.Id, lookup));

private static List<HabitChildResponse> MapChildren(Guid parentId, ILookup<Guid?, Habit> lookup) =>
lookup[parentId]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Orbit.Domain.Entities;
using Orbit.Domain.Enums;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Habits.Queries;

Expand All @@ -28,6 +29,7 @@ public record HabitScheduleItem(
bool ReminderEnabled,
int ReminderMinutesBefore,
bool SlipAlertEnabled,
IReadOnlyList<ChecklistItem> ChecklistItems,
IReadOnlyList<HabitTagItem> Tags,
IReadOnlyList<HabitScheduleChildItem> Children);

Expand All @@ -43,6 +45,7 @@ public record HabitScheduleChildItem(
DateOnly DueDate,
TimeOnly? DueTime,
int? Position,
IReadOnlyList<ChecklistItem> ChecklistItems,
IReadOnlyList<HabitTagItem> Tags,
IReadOnlyList<HabitScheduleChildItem> Children);

Expand Down Expand Up @@ -180,6 +183,7 @@ private static HabitScheduleItem MapToScheduleItem(
h.ReminderEnabled,
h.ReminderMinutesBefore,
h.SlipAlertEnabled,
h.ChecklistItems,
MapTags(h),
MapChildren(h.Id, lookup, dateFrom, dateTo));

Expand Down Expand Up @@ -209,7 +213,7 @@ private static List<HabitScheduleChildItem> MapChildren(Guid parentId, ILookup<G
c.Id, c.Title, c.Description,
c.FrequencyUnit, c.FrequencyQuantity, c.IsBadHabit, c.IsCompleted,
c.Days.ToList(), c.DueDate, c.DueTime,
c.Position, MapTags(c), MapChildren(c.Id, lookup, dateFrom, dateTo)))
c.Position, c.ChecklistItems, MapTags(c), MapChildren(c.Id, lookup, dateFrom, dateTo)))
.ToList();

private static List<HabitTagItem> MapTags(Habit h) =>
Expand Down
6 changes: 5 additions & 1 deletion src/Orbit.Application/Habits/Queries/GetHabitsQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Orbit.Domain.Entities;
using Orbit.Domain.Enums;
using Orbit.Domain.Interfaces;
using Orbit.Domain.ValueObjects;

namespace Orbit.Application.Habits.Queries;

Expand All @@ -17,6 +18,7 @@ public record HabitResponse(
TimeOnly? DueTime,
IReadOnlyList<DayOfWeek> Days,
int? Position,
IReadOnlyList<ChecklistItem> ChecklistItems,
DateTime CreatedAtUtc,
IReadOnlyList<HabitChildResponse> Children);

Expand All @@ -32,6 +34,7 @@ public record HabitChildResponse(
DateOnly DueDate,
TimeOnly? DueTime,
int? Position,
IReadOnlyList<ChecklistItem> ChecklistItems,
IReadOnlyList<HabitChildResponse> Children);

public record GetHabitsQuery(
Expand Down Expand Up @@ -100,6 +103,7 @@ public async Task<IReadOnlyList<HabitResponse>> Handle(GetHabitsQuery request, C
h.DueTime,
h.Days.ToList(),
h.Position,
h.ChecklistItems,
h.CreatedAtUtc,
MapChildren(h.Id, lookup));

Expand All @@ -111,6 +115,6 @@ private static List<HabitChildResponse> MapChildren(Guid parentId, ILookup<Guid?
c.Id, c.Title, c.Description,
c.FrequencyUnit, c.FrequencyQuantity, c.IsBadHabit, c.IsCompleted,
c.Days.ToList(), c.DueDate, c.DueTime,
c.Position, MapChildren(c.Id, lookup)))
c.Position, c.ChecklistItems, MapChildren(c.Id, lookup)))
.ToList();
}
17 changes: 15 additions & 2 deletions src/Orbit.Domain/Entities/Habit.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Orbit.Domain.Common;
using Orbit.Domain.Enums;
using Orbit.Domain.ValueObjects;

namespace Orbit.Domain.Entities;

Expand All @@ -18,6 +19,7 @@ public class Habit : Entity
public bool ReminderEnabled { get; private set; }
public int ReminderMinutesBefore { get; private set; } = 15;
public bool SlipAlertEnabled { get; private set; }
public IReadOnlyList<ChecklistItem> ChecklistItems { get; private set; } = [];
public int? Position { get; private set; }
public DateTime CreatedAtUtc { get; private set; }
public ICollection<System.DayOfWeek> Days { get; private set; } = [];
Expand Down Expand Up @@ -48,7 +50,8 @@ public static Result<Habit> Create(
Guid? parentHabitId = null,
bool reminderEnabled = false,
int reminderMinutesBefore = 15,
bool slipAlertEnabled = false)
bool slipAlertEnabled = false,
IReadOnlyList<ChecklistItem>? checklistItems = null)
{
if (userId == Guid.Empty)
return Result.Failure<Habit>("User ID is required.");
Expand Down Expand Up @@ -77,6 +80,7 @@ public static Result<Habit> Create(
ReminderEnabled = reminderEnabled,
ReminderMinutesBefore = reminderMinutesBefore,
SlipAlertEnabled = slipAlertEnabled,
ChecklistItems = checklistItems ?? [],
CreatedAtUtc = DateTime.UtcNow
});
}
Expand Down Expand Up @@ -104,6 +108,10 @@ public Result<HabitLog> Log(DateOnly date, string? note = null)
{
// Recurring habit: advance DueDate past today
AdvanceDueDate(date);

// Reset checklist for next occurrence
if (ChecklistItems.Count > 0)
ChecklistItems = ChecklistItems.Select(i => i with { IsChecked = false }).ToList();
}

return Result.Success(log);
Expand Down Expand Up @@ -170,7 +178,8 @@ public Result Update(
TimeOnly? dueTime = null,
bool? reminderEnabled = null,
int? reminderMinutesBefore = null,
bool? slipAlertEnabled = null)
bool? slipAlertEnabled = null,
IReadOnlyList<ChecklistItem>? checklistItems = null)
{
if (string.IsNullOrWhiteSpace(title))
return Result.Failure("Title is required.");
Expand Down Expand Up @@ -199,10 +208,14 @@ public Result Update(
ReminderMinutesBefore = reminderMinutesBefore.Value;
if (slipAlertEnabled.HasValue)
SlipAlertEnabled = slipAlertEnabled.Value;
if (checklistItems is not null)
ChecklistItems = checklistItems;

return Result.Success();
}

public void UpdateChecklist(IReadOnlyList<ChecklistItem> items) => ChecklistItems = items;

public void SetPosition(int? position) => Position = position;

public void SetParentHabitId(Guid? parentHabitId) => ParentHabitId = parentHabitId;
Expand Down
2 changes: 2 additions & 0 deletions src/Orbit.Domain/Models/AiAction.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Orbit.Domain.Enums;
using Orbit.Domain.ValueObjects;

namespace Orbit.Domain.Models;

Expand All @@ -19,4 +20,5 @@ public record AiAction
public List<AiAction>? SubHabits { get; init; }
public List<AiAction>? SuggestedSubHabits { get; init; }
public List<string>? TagNames { get; init; }
public List<ChecklistItem>? ChecklistItems { get; init; }
}
3 changes: 3 additions & 0 deletions src/Orbit.Domain/ValueObjects/ChecklistItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Orbit.Domain.ValueObjects;

public record ChecklistItem(string Text, bool IsChecked);
Loading