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
13 changes: 13 additions & 0 deletions src/Orbit.Api/Controllers/HabitsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ public async Task<IActionResult> LogHabit(
: BadRequest(new { error = result.Error });
}

[HttpPost("{id:guid}/skip")]
public async Task<IActionResult> SkipHabit(
Guid id,
CancellationToken cancellationToken)
{
var command = new SkipHabitCommand(HttpContext.GetUserId(), id);
var result = await mediator.Send(command, cancellationToken);

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

[HttpPut("{id:guid}")]
public async Task<IActionResult> UpdateHabit(
Guid id,
Expand Down
49 changes: 49 additions & 0 deletions src/Orbit.Application/Habits/Commands/SkipHabitCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using MediatR;
using Microsoft.Extensions.Caching.Memory;
using Orbit.Application.Common;
using Orbit.Domain.Common;
using Orbit.Domain.Entities;
using Orbit.Domain.Interfaces;

namespace Orbit.Application.Habits.Commands;

public record SkipHabitCommand(Guid UserId, Guid HabitId) : IRequest<Result>;

public class SkipHabitCommandHandler(
IGenericRepository<Habit> habitRepository,
IUserDateService userDateService,
IUnitOfWork unitOfWork,
IMemoryCache cache) : IRequestHandler<SkipHabitCommand, Result>
{
public async Task<Result> Handle(SkipHabitCommand request, CancellationToken cancellationToken)
{
var habit = await habitRepository.FindOneTrackedAsync(
h => h.Id == request.HabitId,
cancellationToken: cancellationToken);

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

if (habit.UserId != request.UserId)
return Result.Failure(ErrorMessages.HabitNotOwned);

if (habit.IsCompleted)
return Result.Failure("Cannot skip a completed habit.");

if (habit.FrequencyUnit is null)
return Result.Failure("Cannot skip a one-time task.");

var today = await userDateService.GetUserTodayAsync(request.UserId, cancellationToken);

if (habit.DueDate > today)
return Result.Failure("Cannot skip a habit that is not yet due.");

habit.AdvanceDueDate(today);

await unitOfWork.SaveChangesAsync(cancellationToken);

CacheInvalidationHelper.InvalidateSummaryCache(cache, habit.UserId);

return Result.Success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FluentValidation;
using Orbit.Application.Habits.Commands;

namespace Orbit.Application.Habits.Validators;

public class SkipHabitCommandValidator : AbstractValidator<SkipHabitCommand>
{
public SkipHabitCommandValidator()
{
RuleFor(x => x.UserId)
.NotEmpty();

RuleFor(x => x.HabitId)
.NotEmpty();
}
}
Loading